Reputation: 8251
If I have a non-nullable model field, remove it, and create a migration, that migration becomes non-reversible:
Consider the following model:
class Foo(models.Model):
bar = models.TextField()
test = models.TextField() # This field is to go away, bye-bye!
And migration:
# app/migrations/003_remove_foo_test.py
class Migration(migrations.Migration):
dependencies = [
('app', '0002_foo_test'),
]
operations = [
migrations.RemoveField(
model_name='foo',
name='test',
),
]
Unapplying this migration throws an exception:
$ src/manage.py migrate app 0002
Operations to perform:
Target specific migration: 0002_foo_test, from app
Running migrations:
Unapplying app.0003_remove_foo_test...Traceback (most recent call last):
...
django.db.utils.IntegrityError: column "test" contains null values
Of course, this is the expected behaviour, it is clearly documented and I'm not asking why this happens:
Bear in mind that when reversed this is actually adding a field to a model; if the field is not nullable this may make this operation irreversible (apart from any data loss, which of course is irreversible).
However, we all make mistakes and sometimes we just need to somehow reverse a field deletion, even if that means manually providing an ad hoc stub value for all reversed non-null fields. For instance, South migrations optionally allow the reversal of such operations (by asking the developer on whether to provide a default for restored fields, or disallow reverse migration), which doesn't seem to be the case with the all new fancy Django 1.7 migrations.
Question: what is the easiest/fastest way to undo a field removal with Django 1.7+ migrations (assuming it has already happened)? It doesn't necessarily need to be fully scripted with Python, a set of manual instructions will do.
Upvotes: 38
Views: 8171
Reputation: 111
Just add 'default' and 'preserve_default' to your AddField or AddModel in older migration, Django will already know that it must recreate the column with the provided default
Upvotes: 1
Reputation: 140
The easiest way may be to use migrations.RunSQL
You can edit the migration so your operations
list would look like this:
operations = [
sql=[('alter table foo_test drop test)],
reverse_sql=[('alter table foo_test add test varchar)]
]
That would be a hacky solution, but so would be probably any other.
Upvotes: 1
Reputation: 20539
You can manually edit your migration and add AlterField
with default value for a field just before RemoveField
. It should be safe even after applying migration. That will make RemoveField
that will happen after to be reversible.
An example. Having field in model summary
named profit
that was defined before removal like that:
profit = models.PositiveIntegerField(verbose_name='profits')
you should add before RemoveField
of it an AlterField
like that:
migrations.AlterField(
model_name='summary',
name='profit',
field=models.PositiveIntegerField(verbose_name='profits', default=0),
preserve_default=False,
),
Upvotes: 32
Reputation: 308779
If you're trying to make future migrations reversible, you could try removing the field as three migrations.
Each of these three steps should be reversible.
If you have already run the migration and need to reverse it, you could
--fake
to the previous migrationUpvotes: 5