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

ygr*_*oel 11 django django-models

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

email = models.EmailField(max_length=255, primary_key=True,)
Run Code Online (Sandbox Code Playgroud)

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

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

  1. 我刚刚用unique = True替换了primary_key = True选项.python manage.py makemigrations抱怨:

您试图在没有默认值的情况下向用户添加不可为空的字段"id"; 我们不能那样做(数据库需要一些东西来填充现有的行).

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

  1. 根据此帖子将主键字段更改为唯一字段,我尝试手动添加自动字段,如下所示:

    id = models.AutoField()

现在python manage.py makemigrations失败了:

login.User.id: (fields.E100) AutoFields must set primary_key=True.
Run Code Online (Sandbox Code Playgroud)

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

  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"列,不知道为什么.

这样做的正确方法是什么?此外,如果成功,是否会正确更新引用我的用户的ForeignKey字段?

sal*_*ara 7

我自己也遇到过这个问题,并最终编写了一个可重用的(不过是 MySQL 特定的)迁移。您可以在此存储库中找到代码。我也在我的博客中写过相关内容。

概括来说,所采取的步骤是:

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

    class Something(models.Model):
        email = models.EmailField(max_length=255, unique=True)
    
    Run Code Online (Sandbox Code Playgroud)
  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),
        ]
    
    Run Code Online (Sandbox Code Playgroud)

    在哪里

    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()
    
    Run Code Online (Sandbox Code Playgroud)
    1. 应用新的迁移


e4c*_*4c5 4

这种情况很难解决,特别是在 sqlite 上,它实际上甚至没有真正的 ALTER TABLE 语句

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

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

第 1 步:创建一个新模型,与

class TempModel(models.Model):
    email = models.EmailField(max_length=255)
    # other fields from your existing model
Run Code Online (Sandbox Code Playgroud)

请注意,您不需要显式声明主键字段。只需在电子邮件字段中将其关闭就足够了。

第2步:进行迁移并迁移

第 3 步:打开您最喜欢的数据库客户端并执行以下操作:

INSERT INTO myapp_tempmodel(fields,....) SELECT * FROM myapp_oldmodel
Run Code Online (Sandbox Code Playgroud)

步骤4:删除旧表,进行迁移并迁移

步骤5:重命名临时表,进行迁移并迁移