Let's say I have the following model:
class Product(models.Model):
name = models.CharField(max_length=128)
is_retired = models.BooleanField(default=False)
I want to remove the is_retired
field. I'm using blue-green deployments to release changes to production so I'm using SeparateDatabaseAndState in my migration.
My initial migration to remove the field from the application state is simple:
class Migration(migrations.Migration):
dependencies = ...
operations = [
migrations.SeparateDatabaseAndState(
state_operations=[
migrations.RemoveField(
model_name="product",
name="is_retired",
),
],
database_operations=[],
),
]
I can successfully run the migration. However, I'm getting the following error when I attempt to create a new product with Product.objects.create(name="Wrench")
:
self = <django.db.backends.sqlite3.base.SQLiteCursorWrapper object at 0x106bf4050>
query = 'INSERT INTO "core_product" ("name") VALUES (?) RETURNING "core_product"."id"'
params = ('Wrench',)
def execute(self, query, params=None):
if params is None:
return super().execute(query)
# Extract names if params is a mapping, i.e. "pyformat" style is used.
param_names = list(params) if isinstance(params, Mapping) else None
query = self.convert_query(query, param_names=param_names)
> return super().execute(query, params)
E django.db.utils.IntegrityError: NOT NULL constraint failed: core_product.is_retired
Looks like the INSERT
query is failing because the is_retired
field is defaulting to null
, instead of its correct default of False
.
I fixed it by making Product.is_retired
nullable before removing it from the state:
class Migration(migrations.Migration):
dependencies = ...
operations = [
migrations.AlterField(
model_name="product",
name="is_retired",
field=models.BooleanField(default=False, null=True),
),
migrations.SeparateDatabaseAndState(
state_operations=[
migrations.RemoveField(
model_name="product",
name="is_retired",
),
],
database_operations=[],
),
]
Now I can successfully create a product with Product.objects.create(name="Wrench")
.
A few questions:
is_retired
default back to False
but couldn't figure it out.Your approach to making the is_retired field nullable before removing it from the state is a valid solution to the problem you encountered. However, let's address your questions directly:
Your approach is a sensible one. By making the field nullable before removing it from the state, you avoid the NOT NULL constraint failure when inserting new records. Another approach could involve manually setting the default value of the field in the database before running the migration to remove it from the state, but this might involve more manual database operations and could be error-prone.
Changing a field from not nullable to nullable should generally be safe, especially if your database can handle null values gracefully. In your case, since you're using a blue-green deployment strategy, the risk is mitigated further as you can test the changes in a separate environment before switching traffic to it. However, it's always a good practice to thoroughly test such changes to ensure they don't have unintended consequences on your application's behavior.