Add integer primary_key in production

60 Views Asked by At

We have a running instance of a Django Backend. On a few model we used an CharField as an Primary Key because it was the best solution for the old requirements. Now we need to add a basic int id like

id = models.AutoField(primary_key=True)

and let the old charfield remain as primary_key=False.

I removed the primary_key=True Flag from the old primary key attribute and triggered ./manage.py makemigrations. If I don't declare any primary key Django is generating its normal id like mentioned above. But after trigger, Djang asks for default values for old entries of this model like:

It is impossible to add a non-nullable field 'id' to anrede without specifying a default. This is because the database needs something to populate existing rows.
Please select a fix:
 1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
 2) Quit and manually define a default value in models.py.

Becaus it's a unique field, I can't use any static content. So how can I still make migrations without deleting old migrations?

2

There are 2 best solutions below

0
Selcuk On BEST ANSWER

The usual way to implement this is:

  1. Add a new (non-unique, nullable) id field to the model and generate the migration scripts.
  2. Write a data migration that populates the id column incrementally for existing records.
  3. Add primary_key=True to the new id field (and remove null=True) and generate the migration scrips.

Now you will have 3 migrations that should do what you want when executed in that order.

Note that you should also reset your auto-increment counter using sqlsequencereset management command. You should also consider foreign keys (if any) that refer to this model.

0
mainbutton.dev On

Had now time to test it. Here my conclusion with detailed code:

  1. Add new PositiveIntegerField(null=True, unique=False) to your old model and create new migration
  2. Add new empty migration to populate old records witch an incremental id. Here ist my migration for that task:
    from django.db import migrations
    
    def populate_old_id(apps, schema_editor):
        Stammdaten = apps.get_model('your_app_name', 'Stammdaten')
        counter = 1
        for stammdaten in Stammdaten.objects.all():
            stammdaten.id = counter
            stammdaten.save()
    
    class Migration(migrations.Migration):
    
        dependencies = [
            ('your_app_name', 'previous_migration_name'),
        ]
    
        operations = [
            migrations.RunPython(populate_old_id),
        ]
  1. remove (primary_key=True) from old pk and add (primary_key=True) to new pk field.Also you need to remove (unique=True, null=True) from your new pk.
  2. When you run ./manage.py makemigrations you will be asked how old records should be handled. Choose option 2) because that is what you do at step 2. If you edit multiple Models at the same time you will be asked for every model seperately. Here an example:
Please select a fix:
 1) Provide a one-off default now (will be set on all existing rows with a null value for this column)
 2) Ignore for now. Existing rows that contain NULL values will have to be handled manually, for example with a RunPython or RunSQL operation.
 3) Quit and manually define a default value in models.py.
Select an option: 2