62
votes

I have a uuid field (not a primary key). The generated migration is:

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),
        ),
        ...
    ]

But when doing python manage.py migrate it is crashing with:

django.db.utils.IntegrityError: could not create unique index "restaurants_device_uuid_key" DETAIL: Key (uuid)=(f3858ded-b8e0-4ac0-8436-8a61b10efc73) is duplicated.

Strangely enough, the problem does not seem to occur with primary keys (which are maybe created by the database, and not internally by django?)

How can I add a uuid field, and make sure that migrations work?

4
The docs explain this pretty well, see Migrations that add unique fields. - knbk
@knbk Thanks for the hint!, I solved my issue by wrapping up all the scripts from the docs to single migrations files. No need to create 2 empty migration scripts - Adiyat Mubarak
The Django documentation suggests creating three migration files to add the new UUID field. An easier way is to add the gen_uuid method into the original migration, change unique=True to null=True in the AddField operation, add the RunPython operation underneath and then follow it with than AlterField operation that replaces the null=True with unique=True. Only a single migration required. - BB1
For whomever stumbles upon this...the link in first comment is broken. Please refer to this one - kingJulian
When the migration crashes, it seems to leave the db and Django in a broken state :/ The migration isn't registred as being run (in showmigrations) but it still has fiddled with the db. I guess the ALTER TABLE ops can't be transacted :) - BjornW

4 Answers

56
votes

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)
        )
    ]
33
votes

(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
4
votes

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.

0
votes

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()