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

3

11

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?

Hamrah answered 11/1, 2018 at 4:58 Comment(6)
any place you have used this model as a foreign relation?Fassold
Yes, I've used itHamrah
then you have remove it first, makemigrations, then change this model with uuid migrate and then use it in the other model and then migrate againFassold
Can you share some code snippet? It would be convenient for me !Hamrah
there is no code for this, its just following the steps, the one i told earlierFassold
Oh I see, Well Thanks for your help !Hamrah
T
35

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

Tumpline answered 13/1, 2018 at 0:30 Comment(1)
Great answer @aliva, however, I have an error at step 7 : django.db.utils.ProgrammingError: cannot cast type uuid to integer. I am sure I did all the steps correctly. What am I missing? Do you know how to fix that? Thanks.Dap
A
1

This is a relatively simple solution, but use this only if you can afford to delete the whole data in the DB currently (you may have just started the project).

Reset the migrations

  • Delete all the migrations in the migrations folder except the init.py file.
  • Run python manage.py makemigrations
  • Run python manage.py migrate

This will create entirely new migrations for your application, and you won't face any type issues in this case as the type of the uuid field gets set in the initial migration field itself.

Argo answered 6/12, 2023 at 21:20 Comment(0)
L
0

I don't have enough rep to add a comment to the answer by @aliva. I believe there is an error with step 5.

for item in RelatedModel.objects.all():
item.base_uuid_id = item.base

drop the .uuid from item.base

I was getting an error Cannot assign "UUID('...')" must be a "Base" instance.

Lilylilyan answered 14/6, 2023 at 7:59 Comment(1)
You can Suggest An Edit, instead of posting a Comment.Hyps

© 2022 - 2024 — McMap. All rights reserved.