84

I have a django project with a database table that already contains data. I'd like to change the field name without losing any of the data in that column. My original plan was to simply change the model field name in a way that would not actually alter the name of the db table (using the db_column column parameter):

The original model:

class Foo(models.Model):
  orig_name = models.CharField(max_length=50)

The new model:

class Foo(models.Model):
  name = models.CharField(max_length=50, db_column='orig_name')

But, running South's schemamigration --auto produces a migration script that deletes the original column, orig_name, and adds a new column, name, which would have the unwanted side effect of deleting the data in that column. (I'm also confused as to why South wants to change the name of the column in the db, since my understanding of db_column was that it enables a change to the model field name without changing the name of the database table column).

If I can't get away with changing the model field without changing the db field, I guess I could do a more straight forward name change like so:

The original model:

class Foo(models.Model):
  orig_name = models.CharField(max_length=50)

The new model:

class Foo(models.Model):
  name = models.CharField(max_length=50)

Regardless of which strategy I end up using (I would prefer the first, but would find the second acceptable), my primary concern is ensuring that I don't lose the data that is already in that column.

Does this require a multi-step process? (such as 1. adding a column, 2. migrating the data from the old column to the new column, and 3. removing the original column) Or can I alter the migration script with something like db.alter_column?

What is the best way to preserve the data in that column while changing the column's name?

sfletche
  • 47,248
  • 30
  • 103
  • 119

11 Answers11

68

Changing the field name while keeping the DB field

Adding an answer for Django 1.8+ (with Django-native migrations, rather than South).

Make a migration that first adds a db_column property, and then renames the field. Django understands that the first is a no-op (because it changes the db_column to stay the same), and that the second is a no-op (because it makes no schema changes). I actually examined the log to see that there were no schema changes...

operations = [
    migrations.AlterField(
        model_name='mymodel',
        name='oldname',
        field=models.BooleanField(default=False, db_column=b'oldname'),
    ),
    migrations.RenameField(
        model_name='mymodel',
        old_name='oldname',
        new_name='newname',
    ),
]
Amichai Schreiber
  • 1,528
  • 16
  • 16
  • I'm not sure if the `AlterField` part is necessary, at least on Django 1.9. I created a migration only with the `RenameField` part and it worked nicely. – luizs81 Mar 16 '17 at 00:42
  • 3
    @luizs81It depends on if you want the Column to be renamed in the database. Without AlterField you'll make a schema change – Aaron McMillin Nov 29 '17 at 19:35
  • Doesn't work in Django 2+. But the same logic works with `related_name` => if this stays the same, Django will detect that the field was renamed. – lapin Oct 02 '19 at 07:07
  • 4
    BE CAREFUL! These operations trigger changes in some cases, like for foreign keys (`DROP/SET CONSTRAINT`). I learned it the hard way (in production). I created filed a ticket to Django and did the pull request to fix these cases. https://code.djangoproject.com/ticket/31825 https://code.djangoproject.com/ticket/31826 That said, you can use the `manage.py sqlmigrate ` to check what SQL commands it will run. It is database dependent, so run it with the same database type you have in production. – iurisilvio Jul 26 '20 at 10:19
  • @iurisilvio What should be done to avoid add/drop constraint. Is there any way to avoid it? – yogi Mar 17 '21 at 01:14
  • @iurisilvio Its something like this. `migrations.AlterField( model_name='invoice', name='payment', field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, to='billing.Payment', db_column='payment'), ), migrations.RenameField( model_name='invoice', old_name='payment', new_name='_deprecated_payment', ),` – yogi Mar 17 '21 at 02:35
  • @iurisilvio And the query looks like `-- Alter field payment on invoice -- SET CONSTRAINTS "fk_invoice_payment" IMMEDIATE; ALTER TABLE "billing_invoice" DROP CONSTRAINT "fk_invoice_payment"; ALTER TABLE "billing_invoice" RENAME COLUMN "payment_id" TO "payment"; ALTER TABLE "billing_invoice" ADD CONSTRAINT "billing_invoice_payment_86817d8a_fk_billing_payment_id" FOREIGN KEY ("payment") REFERENCES "billing_payment" ("id") DEFERRABLE INITIALLY DEFERRED; -- -- Rename field payment on invoice to _deprecated_payment -- COMMIT;` – yogi Mar 17 '21 at 02:35
  • @yogi These issues were fixed in Django 3.1.1. For older versions, you can write a `SeparateDatabaseAndState` migration. It is not well documented, but works great. Check an example here: https://docs.djangoproject.com/en/3.1/howto/writing-migrations/#changing-a-manytomanyfield-to-use-a-through-model – iurisilvio Mar 17 '21 at 07:39
62

Django 2.0.9 (and onwards) can automatically detect if a field was renamed and gives an option to rename instead of delete and create a new one

(same works for Django 2.2) enter image description here

Initial answer

Posting, if it's still helpful for someone.

For Django 2.0 + simply rename the field in the model

class Foo(models.Model):
    orig_name = models.CharField(max_length=50)

to

class Foo(models.Model):
    name = models.CharField(max_length=50)

Now run python manage.py makemigrations It'll generate migration with operations for removing the old field and adding the new one.

Go ahead and change that to following.

operations = [
    migrations.RenameField(
        model_name='foo',
        old_name='orig_name',
        new_name='name')
]

Now run python manage.py migrate it'll rename the column in DB without losing data.

Aarif
  • 1,595
  • 3
  • 17
  • 29
  • well, it does work, Not sure about your use case, can you provide some details? in fact, in **Django 2.0.9 **, it provided the option if I renamed the field. I've updated the answer. – Aarif Feb 06 '19 at 11:03
  • LoL no, because I figured out a different way to do it a long time ago. I'm sorry. – b264 Mar 30 '19 at 16:43
  • 2
    I appreciate your answer @Aarif, that was helpful for me. If you are renaming a field or two, you should be fine with using this method. You may run into a problem, however, if you're renaming many fields at one time (which isn't recommend anyways). – Jihoon Baek Apr 08 '19 at 00:44
  • see my comment re. forms referencing the old column name on Paul Sebastian's answer - those will cause migration errors, (Django 2.2.5). – JL Peyret Dec 28 '19 at 20:30
  • 6
    Just an addendum, if you want to do it this way, make sure you're not changing *anything* else in the field at the same time, or else Django won't detect it as a name change, but instead as a field removal and new field addition. I tried removing `blank=True, null=True` and setting a default value at the same and it wasn't working. Once I reverted that and changed only the name, it worked! – Luca Bezerra Dec 23 '20 at 16:27
  • @LucaBezerra thanks for adding your observation here, I think it should be obvious if you change more than just the name Django won't give you the option to rename the field – Aarif Jan 11 '21 at 06:33
  • @Aarif I wouldn't call anything obvious, especially considering the question's subject is not an advanced one. Also, some changes, like making the field required might look innocuous enough at first glance that one might imagine Django won't have problems dealing with it. On top of that, the fact that a migration is created anyways when you change multiple parts of the field might lead one to believe the process worked the way they wanted, not realizing that their column will be deleted unless they open the migration and check the code, hence the heads up. – Luca Bezerra Jan 11 '21 at 14:54
  • @LucaBezerra Agreed, this just caught me out too. Even changing non-relational properties like `verbose_name` at the same time will prevent Django suggesting the `RenameField` operation. Do any other changes in a separate migration, then rename the field as a final (or first) step. – BigglesZX Jul 16 '21 at 17:09
18

It is quite easy to fix. But you will have to modify the migration yourself.

Instead of dropping and adding the column, use db.rename_column. You can simply modify the migration created by schemamigration --auto

Wolph
  • 78,177
  • 11
  • 137
  • 148
  • nice! i thought there would be a simple solution. So if I add the following to forwards: db.rename_column('myapp.foo', 'orig_name', 'name') and the following to backwards: db.rename_column('myapp.foo', 'name', 'orig_name') should these changes be added to a migration script that includes the change to the model definition? or should I not manually change the model definition before creating the migration script? (if my question is unclear, let me know, and I'll explain it differently) – sfletche Aug 16 '10 at 23:20
  • nevermind, that was dumb question. i'll change the model definition first, then create an --empty migration script, and alter the forwards and backwards methods to include the rename_column data I included above. Thanks! – sfletche Aug 16 '10 at 23:23
  • 1
    example: http://stackoverflow.com/questions/3235995/django-how-to-rename-a-model-field-using-south – andilabs May 27 '15 at 10:28
10

Actually with Django 1.10, just renaming the field in the model and then running makemigrations, immediately identifies the operation (ie. one field disappeared, another appeared in its stead):

$ ./manage.py makemigrations
Did you rename articlerequest.update_at to articlerequest.updated_at (a DateTimeField)? [y/N] y
Migrations for 'article_requests':
  article_requests/migrations/0003_auto_20160906_1623.py:
    - Rename field update_at on articlerequest to updated_at
Paul-Sebastian Manole
  • 2,538
  • 1
  • 32
  • 33
  • 2
    Just want to highlight that Django actually ASKS if you did want to rename (I imagine you can answer NO and cancel the makemigrations that you're running) – Ben Oct 07 '17 at 17:11
  • confirmed. worked on Django 2.2.5 as well. However... the migration started out failing, not because of anything directly to do with the model or the db, but rather a ModelForm that was using the old column name. so you need to clean up the forms as well as the model itself before running the migration. I'd rather the form failed in use rather than a DDL migration error but that's how it is. doing the change in postgres was easy but django migrations totally got stuck then so I reverted the DDL Rename, restored from git and did it properly. – JL Peyret Dec 28 '19 at 20:29
  • @JLPeyret I think you should read the question title, I've tested it with **Django 2.2.9** and it works fine if you've hardcoded the field names, that is your responsibility as a developer to make appropriate changes as far as Django is concerned it is working as expected – Aarif Dec 30 '19 at 07:28
  • @Aarif I totally fail to understand your point. The question was relevant to my renaming concerns, people had differing, some too complicated answers, and Django does generally fix the problem. as to "my responsibility", I would expect a DDL not to fail on a *Form* issue, though I would expect it to fail on a *Model* issue. My comment was made to say it **does** work, with a explanation why it might need care, and was motivated by my error *and* by a comment by someone about something quite possibly similar on *your* answer. I suggest less condescension on your part. – JL Peyret Dec 30 '19 at 12:36
  • My comment was in no way critical of your, correct, answer. – JL Peyret Dec 30 '19 at 12:39
  • @JLPeyret the title of the question is 'migrating django-model field-name...', so if you've hardcoded the field name and as you said 'column name' in the forms then ORM can't go look for that, hence you would need to make changes manually and don't feel bad I wasn't critical of you or your approach, I just tried to calrify if you didn't fully understood that, cheers :) – Aarif Dec 31 '19 at 07:15
  • 1
    CAVEAT: for migrations to detect and ask about the name change, you cannot change the verbose name (optional first positional argument in the field definition). If you change both the field name and verbose name, django migrations removes the old name field and adds the new name field. Solution is to keep the verbose name the same as a first step, run make migrations, and only then change the verbose name. – Rick Graves Mar 15 '20 at 23:30
5

I've run into this situation. I wanted to change the field names in the model but keep the column names the same.

The way I've done it is to do schemamigration --empty [app] [some good name for the migration]. The problem is that as far as South is concerned, changing the field names in the model is a change that it needs to handle. So a migration has to be created. However, we know there is nothing that has to be done on the database side. So an empty migration avoids doing unnecessary operation on the database and yet satisfies South's need to handle what it considers to be a change.

Note that if you use loaddata or use Django's test fixture facility (which uses loaddata behind the scenes). You'll have to update the fixtures to use the new field name because the fixtures are based on the model field names, not the database field names.

For cases where column names do change in the database, I never recommend the use db.rename_column for column migrations. I use the method described by sjh in this answer:

I have added the new column as one schemamigration, then created a datamigration to move values into the new field, then a second schemamigration to remove the old column

As I've noted in a comment on that question, the problem with db.rename_column is that it does not rename constraints together with the column. Whether the issue is merely cosmetic or whether this mean a future migration may fail because it cannot find a constraint is unknown to me.

Community
  • 1
  • 1
Louis
  • 146,715
  • 28
  • 274
  • 320
4

It is possible to rename a field without doing any manual migration file editing:

▶︎ Start with something like this:

class Foo(models.Model):
  old_name = models.CharField(max_length=50)

▶︎ Add db_column=OLD_FIELD_NAME to the original field.

class Foo(models.Model):
  old_name = models.CharField(max_length=50, db_column='old_name')

▶︎ Run: python3 manage.py makemigrations

▶︎ Rename the field from OLD_FIELD_NAME to NEW_FIELD_NAME

class Foo(models.Model):
  new_name = models.CharField(max_length=50, db_column='old_name')

▶︎ Run: python3 manage.py makemigrations

You will be prompted:

Did you rename MODEL.OLD_FIELD_NAME to MODEL.NEW_FIELD_NAME (a ForeignKey)? [y/N] y

This will generate two migration files rather than just one, although both migrations are auto-generated.

This procedure works on Django 1.7+.

David Foster
  • 6,931
  • 4
  • 41
  • 42
2

I ran into this situation on Django 1.7.7. I ended up doing the following which worked for me.

./manage.py makemigrations <app_name> --empty

Added a simple subclass of migrations.RenameField that doesn't touch the database:

class RenameFieldKeepDatabaseColumn(migrations.RenameField):
def database_backwards(self, app_label, schema_editor, from_state, to_state):
    pass

def database_forwards(self, app_label, schema_editor, from_state, to_state):
    pass
alphanumeric0
  • 45
  • 1
  • 5
2

UPDATE In Django 3.1 it is quite simple for changing only one field at a time.

In my case:

The old field name was: is_admin The new field name was: is_superuser

When I make migrations by python manage.py makemigrations it asked me do I want to rename the field or not. And I just hit y to rename. Then I migrate by python manage.py migrate. The terminal history in my case looks like: enter image description here

NOTE: I did not test with more than one field at a time.

belal_bh
  • 151
  • 6
2

This is for Django 4.0. Let's do this with an example.

My original field name was anticipated_end_date, I need to name it tentative_end_date. Follow the following steps to complete the operation

  1. Change the anticipated_end_date to tentative_end_date inside the model
  2. run python manage.py makemigrations. Ideally, it would show the following message

Was your_model_name.anticipated_end_date renamed to your_model_name.tentative_end_date (a DateField)? [y/N]

If it shows this message, then just press y and you are good to migrate, as it will generate the correct migration. However, if makemigrations command does not ask about renaming the model field, then go inside the generated migration and change the operations content the following way:

    operations = [
        migrations.RenameField(
            model_name='your_model_name',
            old_name='anticipated_end_date',
            new_name='tentative_end_date',
        ),
    ]

Now you can run python manage.py migrate.

This way your model field/DB column will be renamed, and your data will not be lost.

Muhammad Zubair
  • 466
  • 5
  • 17
0

As pointed out in the other responses, it is now quite easy to rename a field with no changes on the database using db_column. But the generated migration will actually create some SQL statements. You can verify that by calling ./manage.py sqlmigrate ... on your migration.

To avoid any impact on your database you need to use SeparateDatabaseAndState to indicate to Django that it doesn't need to do something in DB.

I wrote a small article about that if you want to know more about it.

Thom
  • 1,473
  • 2
  • 20
  • 30
-1

1.Edit the field name on the django model

2.Create an empty migration like below:

$ python manage.py makemigrations --empty testApp (testApp is your application name)

  1. Edit the empty migration file which is created recently

    operations = [ migrations.RenameField('your model', 'old field’, 'new field'), ]

  2. Apply the migration
    $ python manage.py migrate

Database column name will be altered with new name.