我创建了一个电子邮件地址为自定义主键的模型,如下所示:
email = models.EmailField(max_length=255, primary_key=True,)
现在我意识到在我的情况下这不是一个好主意,我想回到自动生成的id字段作为主键。
怎么做?我以不同的方式尝试了这一点,但都失败了。我正在使用带有Python 3.4.3的Django 1.10.4和SQLite数据库。
python manage.py makemigrations
抱怨:您正在尝试添加一个不可为空的字段' id'没有默认的用户;我们不能这样做(数据库需要一些东西来填充现有的行)。
如果我将0
指定为默认值,python manage.py migrate
将失败并显示django.db.utils.IntegrityError: UNIQUE constraint failed: login_user.id
根据这篇文章Change Primary Key field to unique field我尝试手动添加自动对帐,如:
id = models.AutoField()
现在python manage.py makemigrations
失败了:
login.User.id: (fields.E100) AutoFields must set primary_key=True.
如果按照错误消息的建议执行操作,我会遇到与第一次尝试时相同的问题:缺少默认值。
makemigrations
工作正常,但migrate
失败并带有追溯和此错误:django.db.utils.OperationalError: duplicate column name: id
似乎正在尝试创建一个额外的' id'专栏,不知道为什么。这样做的正确方法是什么?此外,如果成功,那么引用我的用户的ForeignKey字段是否会正确更新?
答案 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中写了这篇文章。
总结一下,采取的步骤是:
像这样修改你的模型类:
class Something(models.Model):
email = models.EmailField(max_length=255, unique=True)
沿着以下行添加新迁移:
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()