6👍
You have lot of problems in here, there are 3 steps to achieve your goal.
-
If you have data already in your model you have to define it as
null=True
and remove thedefault=uuid.uuid4
. Why? Because if you put a default value, it will try to add the same default address to all the models when you run your migration so it will of course give you the errordjango.db.utils.IntegrityError: (1062, "Duplicate entry '3ba46cb5f7f340ffa43e348cb789901a' for key 'carbon.uuid'")
because use the same uuid for each row. -
Create a migration to update your existing rows in the model and put them a uuid as following:
phython src/manage.py makemigrations app --name set_uuid_mymodel --empty
# Generated by Django A.B on YYYY-MM-DD HH:MM
from django.db import migrations
import uuid
def gen_uuid(apps, schema_editor):
MyModel = apps.get_model('myapp', 'MyModel')
for row in MyModel.objects.all():
row.uuid = uuid.uuid4()
row.save(update_fields=['uuid'])
class Migration(migrations.Migration):
dependencies = [
('myapp', '0004_add_uuid_field'),
]
operations = [
# omit reverse_code=... if you don't want the migration to be reversible.
migrations.RunPython(gen_uuid, reverse_code=migrations.RunPython.noop),
]
Look at the official documentation: https://docs.djangoproject.com/en/3.0/howto/writing-migrations/#migrations-that-add-unique-fields
Then you can manage new rows generation uuid in the save
method of the model:
def save(self, *args, **kwargs):
''' On save, update uuid and ovoid clash with existing code '''
if not self.uuid:
self.uuid = uuid.uuid4()
return super(YourModel, self).save(*args, **kwargs)
3👍
To add a new required field (null=False
) for a model, you should include a default value. This value will be applied to all existing records.
For UUIDField
, during migrations, Django does not create a new UUID value for each record. A single value is generated through uuid.uuid4()
and applied to all records using a single SQL. So, knowing that unique=True
is applied at database-level, if you have existing records on your database, during migration an IntegrityError
will be raised for this reason.
If you need the existing records with a valid uuid value (you cannot recreate the entire model migration with the uuid
field or just drop the existing records before run the migration) is possible force a new value for each record using a RunPython
operation inside your migration.
Edit your migration to do the following steps:
- Add a nullable
uuid
field, withoutunique
anddefault
settings, so Django can create the column without integrity errors. - Add a custom Python code to force the uuid for existing records
- Alter the
uuid
field, recovering the original settings
import uuid
from django.db import migrations
def force_uuid(apps, schema_editor):
# We get the model from the versioned app registry;
# if we directly import it, it'll be the wrong version
MyModel = apps.get_model('myapp', 'MyModel')
db_alias = schema_editor.connection.alias
for instance in MyModel.objects.using(db_alias).all():
instance.uuid = uuid.uuid4()
instance.save()
class Migration(migrations.Migration):
dependencies = []
operations = [
# Pre generate the uuid field (nullable)
migrations.AddField(
model_name='mymodel',
name='uuid',
field=models.UUIDField(null=True, unique=False, editable=False),
),
# Force uuid on existing records
migrations.RunPython(force_uuid),
# Recovering original settings
migrations.AlterField(
model_name='mymodel',
name='uuid',
field=models.UUIDField(
default=uuid.uuid4, null=False, unique=True, editable=False
),
),
]
Notes
- If you have a lot of records to generate a unique uuid, you can benefit from
bulk_create()
instead loop over all records and adds a lot of single inserts