如何从自定义主键迁移到默认ID

时间:2016-12-16 13:30:45

标签: django django-models

我创建了一个电子邮件地址为自定义主键的模型,如下所示:

email = models.EmailField(max_length=255, primary_key=True,)

现在我意识到在我的情况下这不是一个好主意,我想回到自动生成的id字段作为主键。

怎么做?我以不同的方式尝试了这一点,但都失败了。我正在使用带有Python 3.4.3的Django 1.10.4和SQLite数据库。

  1. 我刚刚用unique = True替换了primary_key = True选项。 python manage.py makemigrations抱怨:
  2. 您正在尝试添加一个不可为空的字段' id'没有默认的用户;我们不能这样做(数据库需要一些东西来填充现有的行)。

    如果我将0指定为默认值,python manage.py migrate将失败并显示django.db.utils.IntegrityError: UNIQUE constraint failed: login_user.id

    1. 根据这篇文章Change Primary Key field to unique field我尝试手动添加自动对帐,如:

      id = models.AutoField()

    2. 现在python manage.py makemigrations失败了:

      login.User.id: (fields.E100) AutoFields must set primary_key=True.
      

      如果按照错误消息的建议执行操作,我会遇到与第一次尝试时相同的问题:缺少默认值。

      1. 我尝试创建一个字段id = IntegerField(unique = True)(在https://docs.djangoproject.com/en/1.10/howto/writing-migrations/#migrations-that-add-unique-fields后面的Django文档中),然后将字段类型更改为AutoField(primary_key = True)。同时,我需要将email字段更改为unique = True以避免使用两个主键。完成这些更改后,makemigrations工作正常,但migrate失败并带有追溯和此错误:django.db.utils.OperationalError: duplicate column name: id似乎正在尝试创建一个额外的' id'专栏,不知道为什么。
      2. 这样做的正确方法是什么?此外,如果成功,那么引用我的用户的ForeignKey字段是否会正确更新?

2 个答案:

答案 0 :(得分:2)

这种情况很难解决,特别是在sqlite上,它实际上没有real ALTER TABLE statement

  

SQLite支持ALTER TABLE的有限子集。 ALTER TABLE   SQLite中的命令允许用户重命名表或添加新表   列到现有表。

大多数类型,django正在通过临时表进行更改。所以你也可以这样做

第1步:创建一个与

完全相同的新模型
class TempModel(models.Model):
    email = models.EmailField(max_length=255)
    # other fields from your existing model

请注意,您不需要明确声明主键字段。仅在电子邮件字段中关闭它就足够了。

第2步:进行迁移和迁移

步骤4:打开您喜欢的数据库客户端并执行INSERT INTO myapp_tempmodel(fields,....)SELECT * FROM myapp_oldmodel

第4步:删除旧表,进行迁移和迁移

第5步:重命名临时表,进行迁移和迁移

答案 1 :(得分:1)

我自己遇到过这个问题,最后写了一个可重用的(特定于MySQL的)迁移。您可以在此repo中找到代码。我也在我的blog中写了这篇文章。

总结一下,采取的步骤是:

  1. 像这样修改你的模型类:

    class Something(models.Model):
        email = models.EmailField(max_length=255, unique=True)
    
  2. 沿着以下行添加新迁移:

    app_name = 'app'
    model_name = 'something'
    related_model_name = 'something_else'
    model_table = '%s_%s' % (app_name, model_name)
    pivot_table = '%s_%s_%ss' % (app_name, related_model_name, model_name)
    fk_name, index_name = None, None 
    
    
    class Migration(migrations.Migration):
    
        operations = [
            migrations.AddField(
                model_name=model_name,
                name='id',
                field=models.IntegerField(null=True),
                preserve_default=True,
            ),
            migrations.RunPython(do_most_of_the_surgery),
            migrations.AlterField(
                model_name=model_name,
                name='id',
                field=models.AutoField(
                    verbose_name='ID', serialize=False, auto_created=True,
                    primary_key=True),
                preserve_default=True,
            ),
            migrations.AlterField(
                model_name=model_name,
                name='email',
                field=models.EmailField(max_length=255, unique=True),
                preserve_default=True,
            ),
            migrations.RunPython(do_the_final_lifting),
        ]
    

    ,其中

    def do_most_of_the_surgery(apps, schema_editor):
        models = {}
        Model = apps.get_model(app_name, model_name)
    
        # Generate values for the new id column
        for i, o in enumerate(Model.objects.all()):
            o.id = i + 1
            o.save()
            models[o.email] = o.id
    
        # Work on the pivot table before going on
        drop_constraints_and_indices_in_pivot_table()
    
        # Drop current pk index and create the new one
        cursor.execute(
            "ALTER TABLE %s DROP PRIMARY KEY" % model_table
        )
        cursor.execute(
            "ALTER TABLE %s ADD PRIMARY KEY (id)" % model_table
        )
    
        # Rename the fk column in the pivot table
        cursor.execute(
            "ALTER TABLE %s "
            "CHANGE %s_id %s_id_old %s NOT NULL" %
            (pivot_table, model_name, model_name, 'VARCHAR(255)'))
        # ... and create a new one for the new id
        cursor.execute(
            "ALTER TABLE %s ADD COLUMN %s_id INT(11)" %
            (pivot_table, model_name))
    
        # Fill in the new column in the pivot table
        cursor.execute("SELECT id, %s_id_old FROM %s" % (model_name, pivot_table))
        for row in cursor:
            id, key = row[0], row[1]
            model_id = models[key]
    
            inner_cursor = connection.cursor()
            inner_cursor.execute(
                "UPDATE %s SET %s_id=%d WHERE id=%d" %
                (pivot_table, model_name, model_id, id))
    
        # Drop the old (renamed) column in pivot table, no longer needed
        cursor.execute(
            "ALTER TABLE %s DROP COLUMN %s_id_old" %
            (pivot_table, model_name))
    
    def do_the_final_lifting(apps, schema_editor):
        # Create a new unique index for the old pk column
        index_prefix = '%s_id' % model_table
        new_index_prefix = '%s_email' % model_table
        new_index_name = index_name.replace(index_prefix, new_index_prefix)
    
        cursor.execute(
            "ALTER TABLE %s ADD UNIQUE KEY %s (%s)" %
            (model_table, new_index_name, 'email'))
    
        # Finally, work on the pivot table
        recreate_constraints_and_indices_in_pivot_table()
    
    1. 应用新迁移