Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to make UUID field default when there's already ID field

I'm working on a project and problem is I've already created models with simple ID column but now my requirements are changed and I want to replace ID field (model) with UUID field I just updated my model:

uuid = models.UUIDField(primary_key=True, default=uuid.uuid4)

but when I run my migrations I got an error

django.db.utils.OperationalError: (1829, "Cannot drop column 'id': needed in a foreign key constraint

Please guide me how can I perform this migration?

like image 988
Mohammad Hani Avatar asked Jan 11 '18 04:01

Mohammad Hani


1 Answers

here are the list of actions that you need to do

1 - add the new uuid field to model (I name this model Base) then generate migration files

uuid = models.UUIDField(default=uuid4, blank=True, null=True)

  • note that uuid is not primarykey yet
  • note that blank = null =True

2- in this step you should populate uuid field with valid data. you should write a data migration file for Base model. please check docs for more info

your forwards method should be something like this:

for item in Base.objects.all():
    item.uuid = uuid4()
    item.save()

3- change uuid field to this and generate migrations

uuid = models.UUIDField(default=uuid4, unique=True)

  • note that uuid is not primarykey yet, but it's unique now

4 - for other models which are pointing to Base model you should add a new foreignkey pointing to uuid field

assumig your default relation to Base model is something like this

base = models.ForeignKey(
    Base, on_delete=models.PROTECT, related_name='base'
)

you should add a temprory field like this

base_uuid = models.ForeignKey(
    Base,
    on_delete=models.PROTECT,
    related_name='base_uuid',
    to_field='uuid',
    blank=True,
    null=True,
)
  • note that I have explicitly defined to_field which tell django this foreign key is not pointing to default pk field
  • you should keep old foreign key field
  • also set blank = null = True
  • for all foreignkeys/manytomanyfield pointing to Base you should add this temp field

5- in this step you should create a data migration file,

in this data migration file you need to fill all base_uuid fields with valid data (based on old base field) your migration code can be something like this

for item in RelatedModel.objects.all():
    item.base_uuid_id = item.base.uuid
  • after this step all base_uuid fields in all models should contain a valid data

6- in all related models drop related field (keep the new base_uuid field but discard old related field) and generate a migration file

7- delete the db_constraint on all FK fields - this is needed, because django will connect through the unique constraint from base_uuid which will be dropped if we change uuid to pk.

base_uuid = models.ForeignKey(
    Base,
    on_delete=models.PROTECT,
    related_name='base_uuid',
    to_field='uuid',
    blank=True,
    null=True,
    db_constraint=False
)

8- in Base model change uuid field and generate a migration file

uuid = models.UUIDField(default=uuid4, primary_key=True)

  • uuid is now a primary key!

9- in all related models (models which you have added base_uuid fields) update the field

  • rename field name (remove _uuid from column name) (make this a individual Migration!)
  • change relate_name field name (remove _uuid from related name)
  • remove blank = null = true if necessary
  • remove to_field='uuid' argument (you don't need that any more)

Very Important Note: run these code with test data and create a full backup from your database before running this code on your actual data

like image 147
aliva Avatar answered Nov 01 '22 13:11

aliva