使用 uuid 字段的 Django 迁移会生成重复的值

我有一个 uuid字段(不是主键)。生成的迁移是:

from __future__ import unicode_literals


from django.db import migrations, models
import uuid




class Migration(migrations.Migration):


dependencies = [
....
]


operations = [
...
migrations.AddField(
model_name='device',
name='uuid',
field=models.UUIDField(default=uuid.uuid4, unique=True),
),
...
]

但是当做 python manage.py migrate的时候,它就崩溃了:

IntegrityError: 无法创建唯一索引 “ Restaurant _ device _ uuid _ Key”详细信息: Key (uuid) = (f3858ded-b8e0-4ac0-8436-8a61b10efc73)被复制。

奇怪的是,主键似乎没有出现问题(主键可能是由数据库创建的,而不是由 django 在内部创建的)

如何添加 uuid 字段,并确保迁移正常工作?

23487 次浏览

In the mode, you have configured, that you want unique values for the uuid fields, but with default values(the same for all). So if you have two 'device' objects in the database, the migrations add 'uuid' field to them with the default 'uuid.uuid4' value and when it tries to set it to the second one, it crashes because of the unique constrains.

If you drop your db and create new objects probably there will be not problems but thats not a solution for production db obviously :D.

A better solution is to create a data migration which sets different uuid value (generated by the default 'uuid' library) to every existing object in the database. You can read more about data migrations here: https://docs.djangoproject.com/en/1.10/topics/migrations/#data-migrations

Then, when you create new objects, django will generate different uuid automatically. ;)

For the primary keys: Django adds it to the model by default.

(Answer taken from the first comment)

See the django docs - Migrations that add unique fields

They recommend changing your single migration into three separate migrations:

  1. Create field, set to null but not unique
  2. Generate unique UUIDs
  3. Alter the field to be unique

Here is an example doing everything in one single migration thanks to a RunPython call.

# -*- coding: utf-8 -*
from __future__ import unicode_literals


from django.db import migrations, models
import uuid




def create_uuid(apps, schema_editor):
Device = apps.get_model('device_app', 'Device')
for device in Device.objects.all():
device.uuid = uuid.uuid4()
device.save()




class Migration(migrations.Migration):


dependencies = [
('device_app', 'XXXX'),
]


operations = [
migrations.AddField(
model_name='device',
name='uuid',
field=models.UUIDField(blank=True, null=True),
),
migrations.RunPython(create_uuid),
migrations.AlterField(
model_name='device',
name='uuid',
field=models.UUIDField(unique=True)
)
]

You can provide a management command to populate the uuid field after uu_id column is created in the model but this has to be done after migrating the model and setting the field default as None:

from django.core.management.base import BaseCommand
from django.apps import apps
import uuid




class Command(BaseCommand):
def handle(self, *args, **options):
classes()




def classes():
app_models = apps.get_app_config('appname').get_models()
for model in app_models:
field = None
try:
field = model._meta.get_field('uu_id')
except:
pass


if field:


uu_id_list = list(model.objects.all().values_list('uu_id',flat=True))
if None in uu_id_list:
for row in model.objects.all():
row.uu_id = uuid.uuid4()
row.save()