迁移 django 模型字段名称更改而不丢失数据

我有一个 django 项目,它的数据库表已经包含了数据。我希望在不丢失该列中任何数据的情况下更改字段名。我最初的计划是简单地更改模型字段名,而不会实际更改 db 表的名称(使用 db_column列参数) :

原始模型:

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

新模式:

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

但是,运行 South 的 schemamigration --auto会生成一个迁移脚本,删除原始列 orig_name,并添加一个新列 name,这将产生不必要的副作用,即删除该列中的数据。(我也不明白为什么 South 要更改 db 中列的名称,因为我对 db _ column 的理解是,它允许更改模型字段名称,而不更改数据库表列的名称)。

如果我不能在不改变 db 字段的情况下更改模型字段,我想我可以像下面这样做一个更直接的名称更改:

原始模型:

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

新模式:

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

无论我最终使用哪种策略(我更喜欢第一种,但会发现第二种是可以接受的) ,我主要关心的是确保不会丢失该列中已有的数据。

这是否需要一个多步骤的过程?(例如1。加一列,2。将数据从旧列迁移到新列,3。删除原来的栏目) 或者我可以用类似 db.alter_column的东西修改迁移脚本吗?

在更改列名的同时保存该列中的数据的最佳方法是什么?

60915 次浏览

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

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.

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',
),
]

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

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+.

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

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.

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.

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.

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.

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.