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

luc*_*luc 34 python django data-migration django-south

我有一个处于BETA模式的应用程序.这个应用程序的模型有一些带有显式primary_key的类.因此Django使用字段并且不会自动创建id.

class Something(models.Model):
    name = models.CharField(max_length=64, primary_key=True)
Run Code Online (Sandbox Code Playgroud)

我认为这是一个坏主意(在django admin中保存对象时看到unicode错误),我想回去并为我的模型的每个类都有一个id.

class Something(models.Model):
    name = models.CharField(max_length=64, db_index=True)
Run Code Online (Sandbox Code Playgroud)

我已经对我的模型进行了更改(通过db_index = True替换每个primary_key = True)并且我想要使用south迁移数据库.

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

我正在评估此问题的不同解决方法.有什么建议?

谢谢你的帮助

S.L*_*ott 65

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

正式主键应始终是代理键.从来没有别的.[强烈的话语.自1980年代以来一直是数据库设计师.重要的经验教训就是:一切都是可以改变的,即使用户在母亲的坟墓上发誓价值无法改变,这确实是一个可以作为主要的自然关键.这不是主要的.只有代理才能成为主要代理人.

你正在进行心内直视手术.不要乱用模式迁移.您正在替换架构.

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

  2. 从旧架构中删除要更改的表.

    依赖于这些表的表将改变其FK; 你可以在适当的位置更新行,或者也可以更简单地删除和重新插入这些行.

  3. 创建新架构.这只会创建正在变化的表.

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

    你有两个案例.

    • 更改PK更改的表将被插入并将获得新的PK.这些必须"级联"到其他表格,以确保其他表格的FK也会发生变化.

    • 具有更改的FK的表必须在外表中找到该行并更新其FK引用.

另类.

  1. 重命名所有旧表.

  2. 创建整个新架构.

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

  4. 删除重命名的旧表.

 


use*_*391 9

要使用south更改主键,可以在datamigration中使用south.db.create_primary_key命令.要将自定义CharField pk更改为标准AutoField,您应该:

1)在模型中创建新字段

class MyModel(Model):
    id = models.AutoField(null=True)
Run Code Online (Sandbox Code Playgroud)

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

class MyRelatedModel(Model):
    fake_fk = models.IntegerField(null=True)
Run Code Online (Sandbox Code Playgroud)

2)创建自动南迁移和迁移:

./manage.py schemamigration --auto
./manage.py migrate
Run Code Online (Sandbox Code Playgroud)

3)创建新的数据迁移

./manage.py datamigration <your_appname> fill_id
Run Code Online (Sandbox Code Playgroud)

在tis datamigration中用数字填充这些新的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'])
Run Code Online (Sandbox Code Playgroud)

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

id = models.AutoField(primary_key=True)
Run Code Online (Sandbox Code Playgroud)

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

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

6)最后一步 - 恢复火关键关系.再次创建真正的fk字段,删除你的fake_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')
Run Code Online (Sandbox Code Playgroud)

所以之前填充的fake_fk变成了一个包含实际关系数据的列,并且在上述所有步骤之后它不会丢失.

  • 你真的试过这个吗?你不能拥有一个不是主要的autofield,也不能有一个允许null的自动对象(南方不允许它,我不认为django会).因此,一旦更改了主键,就无法进行相关的查找. (5认同)
  • @marcin,你是如何适应第一步的(克服南/ django对null,nonpk AutoField的禁令) (4认同)
  • 也就是说,这种方法的改编版本确实对我有用. (3认同)
  • 有没有人成功使用这种方法或这种方法的修改版本来更改主键字段? (3认同)

Tob*_*obu 6

目前您失败了,因为您要添加一个破坏NOT NULL和UNIQUE要求的pk列.

您应该将迁移分成几个步骤,将模式迁移和数据迁移分开:

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


Rem*_*ich 6

我当天遇到了同样的问题,并得出了上述答案的灵感解决方案.

我的模型有一个"位置"表.它有一个名为"unique_id"的CharField,我去年愚蠢地把它作为主键.当然,他们并没有像当时预期的那样独特.还有一个"ScheduledMeasurement"模型,它具有"Location"的外键.

现在我想纠正这个错误并给Location一个普通的自动递增主键.

采取的步骤:

  1. 创建CharField ScheduledMeasurement.temp_location_unique_id和模型TempLocation,以及创建它们的迁移.TempLocation具有我想要的位置结构.

  2. 创建一个数据迁移,使用外键设置所有temp_location_unique_id,并将所有数据从Location复制到TempLocation

  3. 使用迁移删除外键和位置表

  4. 按照我想要的方式重新创建Location模型,使用null = True重新创建外键.将'unique_id'重命名为'location_code'...

  5. 创建使用TempLocation填充位置中的数据的数据迁移,并使用temp_location填充ScheduledMeasurement中的外键

  6. 在外键中删除temp_location,TempLocation和null = True

并编辑假定unique_id是唯一的所有代码(所有objects.get(unique_id = ...)东西),并使用unique_id否则...


小智 5

我设法通过django 1.10.4迁移和mysql 5.5做到了这一点,但这并不容易。

我有一个带有几个外键的varchar主键。我添加了一个id字段,迁移了数据和外键。这是这样的:

  1. 添加将来的主键字段。我id = models.IntegerField(default=0)在主模型中添加了一个字段并生成了自动迁移。
  2. 简单的数据迁移以生成新的主键:

    def fill_ids(apps, schema_editor):
       Model = apps.get_model('<module>', '<model>')
       for id, code in enumerate(Model.objects.all()):
           code.id = id + 1
           code.save()
    
    class Migration(migrations.Migration):
        dependencies = […]
        operations = [migrations.RunPython(fill_ids)]
    
    Run Code Online (Sandbox Code Playgroud)
  3. 迁移现有外键。我写了一个组合迁移:

    def change_model_fks(apps, schema_editor):
        Model = apps.get_model('<module>', '<model>')  # Our model we want to change primary key for
        FkModel = apps.get_model('<module>', '<fk_model>')  # Other model that references first one via foreign key
    
        mapping = {}
        for model in Model.objects.all():
            mapping[model.old_pk_field] = model.id  # map old primary keys to new
    
        for fk_model in FkModel.objects.all():
            if fk_model.model_id:
                fk_model.model_id = mapping[fk_model.model_id]  # change the reference
                fk_model.save()
    
    class Migration(migrations.Migration):
        dependencies = […]
        operations = [
            # drop foreign key constraint
            migrations.AlterField(
                model_name='<FkModel>',
                name='model',
                field=models.ForeignKey('<Model>', blank=True, null=True, db_constraint=False)
            ),
    
            # change references
            migrations.RunPython(change_model_fks),
    
            # change field from varchar to integer, drop index
            migrations.AlterField(
                model_name='<FkModel>',
                name='model',
                field=models.IntegerField('<Model>', blank=True, null=True)
            ),
        ]
    
    Run Code Online (Sandbox Code Playgroud)
  4. 交换主键并还原外键。再次,自定义迁移。当我a)primary_key=True从旧主键删除,b)删除id字段时,我自动生成了此迁移的基础

    class Migration(migrations.Migration):
        dependencies = […]
        operations = [
            # Drop old primary key
            migrations.AlterField(
                model_name='<Model>',
                name='<old_pk_field>',
                field=models.CharField(max_length=100),
            ),
    
            # Create new primary key
            migrations.RunSQL(
                ['ALTER TABLE <table> CHANGE id id INT (11) NOT NULL PRIMARY KEY AUTO_INCREMENT'],
                ['ALTER TABLE <table> CHANGE id id INT (11) NULL',
                 'ALTER TABLE <table> DROP PRIMARY KEY'],
                state_operations=[migrations.AlterField(
                    model_name='<Model>',
                    name='id',
                    field=models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
                )]
            ),
    
            # Recreate foreign key constraints
            migrations.AlterField(
                model_name='<FkModel>',
                name='model',
                field=models.ForeignKey(blank=True, null=True, to='<module>.<Model>'),
        ]
    
    Run Code Online (Sandbox Code Playgroud)