#36545: On Postgres, removing a generated field and its dependent field generates
an invalid migration with makemigrations
-----------------------------+--------------------------------------
Reporter: john-parton | Type: Bug
Status: new | Component: Migrations
Version: 5.2 | Severity: Normal
Keywords: | Triage Stage: Unreviewed
Has patch: 0 | Needs documentation: 0
Needs tests: 0 | Patch needs improvement: 0
Easy pickings: 0 | UI/UX: 0
-----------------------------+--------------------------------------
When running 'makemigrations' to remove a GeneratedField and a field that
it depends, produces a migration that cannot run.
Consider the following example model:
{{{
class ProductImage(models.Model):
type = models.TextField()
is_dupe = models.BooleanField(editable=False, null=True, default=None)
visible = models.GeneratedField(
expression=(
Case(
When(
Q(type="hidden") | Q(is_dupe=True),
then=Value(False),
),
default=Value(True),
)
),
output_field=models.BooleanField(),
db_persist=True,
)
}}}
Removing the `is_dupe` and `visible` fields produces the expected
migration:
{{{
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
("catalog", "0259_alter_productimage_options"),
]
operations = [
migrations.RemoveField(
model_name="productimage",
name="is_dupe",
),
migrations.RemoveField(
model_name="productimage",
name="visible",
),
]
}}}
Running the migration produces this error
{{{
File "/home/john/Code/ecom/.venv/lib/python3.13/site-
packages/django/db/backends/utils.py", line 103, in _execute
return self.cursor.execute(sql)
~~~~~~~~~~~~~~~~~~~^^^^^
File "/home/john/Code/ecom/.venv/lib/python3.13/site-
packages/psycopg/cursor.py", line 97, in execute
raise ex.with_traceback(None)
psycopg.errors.UndefinedColumn: column "visible" of relation
"catalog_productimage" does not exist
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/home/john/Code/ecom/code/src/./manage.py", line 30, in <module>
execute_from_command_line(sys.argv)
~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^
File "/home/john/Code/ecom/.venv/lib/python3.13/site-
packages/django/core/management/__init__.py", line 442, in
execute_from_command_line
utility.execute()
~~~~~~~~~~~~~~~^^
File "/home/john/Code/ecom/.venv/lib/python3.13/site-
packages/django/core/management/__init__.py", line 436, in execute
self.fetch_command(subcommand).run_from_argv(self.argv)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^
File "/home/john/Code/ecom/.venv/lib/python3.13/site-
packages/django/core/management/base.py", line 416, in run_from_argv
self.execute(*args, **cmd_options)
~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^
File "/home/john/Code/ecom/.venv/lib/python3.13/site-
packages/django/core/management/base.py", line 460, in execute
output = self.handle(*args, **options)
File "/home/john/Code/ecom/.venv/lib/python3.13/site-
packages/django/core/management/base.py", line 107, in wrapper
res = handle_func(*args, **kwargs)
File "/home/john/Code/ecom/.venv/lib/python3.13/site-
packages/django/core/management/commands/migrate.py", line 353, in handle
post_migrate_state = executor.migrate(
targets,
...<3 lines>...
fake_initial=fake_initial,
)
File "/home/john/Code/ecom/.venv/lib/python3.13/site-
packages/django/db/migrations/executor.py", line 135, in migrate
state = self._migrate_all_forwards(
state, plan, full_plan, fake=fake, fake_initial=fake_initial
)
File "/home/john/Code/ecom/.venv/lib/python3.13/site-
packages/django/db/migrations/executor.py", line 167, in
_migrate_all_forwards
state = self.apply_migration(
state, migration, fake=fake, fake_initial=fake_initial
)
File "/home/john/Code/ecom/.venv/lib/python3.13/site-
packages/django/db/migrations/executor.py", line 255, in apply_migration
state = migration.apply(state, schema_editor)
File "/home/john/Code/ecom/.venv/lib/python3.13/site-
packages/django/db/migrations/migration.py", line 132, in apply
operation.database_forwards(
~~~~~~~~~~~~~~~~~~~~~~~~~~~^
self.app_label, schema_editor, old_state, project_state
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/john/Code/ecom/.venv/lib/python3.13/site-
packages/django/db/migrations/operations/fields.py", line 174, in
database_forwards
schema_editor.remove_field(
~~~~~~~~~~~~~~~~~~~~~~~~~~^
from_model, from_model._meta.get_field(
self.name)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/john/Code/ecom/.venv/lib/python3.13/site-
packages/django/db/backends/base/schema.py", line 829, in remove_field
self.execute(sql)
~~~~~~~~~~~~^^^^^
File "/home/john/Code/ecom/.venv/lib/python3.13/site-
packages/pgtrigger/migrations.py", line 483, in execute
return super().execute(*args, **kwargs)
~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^
File "/home/john/Code/ecom/.venv/lib/python3.13/site-
packages/django/db/backends/postgresql/schema.py", line 48, in execute
return super().execute(sql, None)
~~~~~~~~~~~~~~~^^^^^^^^^^^
File "/home/john/Code/ecom/.venv/lib/python3.13/site-
packages/django/db/backends/base/schema.py", line 204, in execute
cursor.execute(sql, params)
~~~~~~~~~~~~~~^^^^^^^^^^^^^
File "/home/john/Code/ecom/.venv/lib/python3.13/site-
packages/django/db/backends/utils.py", line 122, in execute
return super().execute(sql, params)
~~~~~~~~~~~~~~~^^^^^^^^^^^^^
File "/home/john/Code/ecom/.venv/lib/python3.13/site-
packages/django/db/backends/utils.py", line 79, in execute
return self._execute_with_wrappers(
~~~~~~~~~~~~~~~~~~~~~~~~~~~^
sql, params, many=False, executor=self._execute
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
)
^
File "/home/john/Code/ecom/.venv/lib/python3.13/site-
packages/django/db/backends/utils.py", line 92, in _execute_with_wrappers
return executor(sql, params, many, context)
File "/home/john/Code/ecom/.venv/lib/python3.13/site-
packages/django/db/backends/utils.py", line 100, in _execute
with self.db.wrap_database_errors:
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/john/Code/ecom/.venv/lib/python3.13/site-
packages/django/db/utils.py", line 91, in __exit__
raise dj_exc_value.with_traceback(traceback) from exc_value
File "/home/john/Code/ecom/.venv/lib/python3.13/site-
packages/django/db/backends/utils.py", line 103, in _execute
return self.cursor.execute(sql)
~~~~~~~~~~~~~~~~~~~^^^^^
File "/home/john/Code/ecom/.venv/lib/python3.13/site-
packages/psycopg/cursor.py", line 97, in execute
raise ex.with_traceback(None)
django.db.utils.ProgrammingError: column "visible" of relation
"catalog_productimage" does not exist
}}}
Manually reordering the migration so that generated field is deleted
before the dependent field:
{{{
class Migration(migrations.Migration):
dependencies = [
("catalog", "0259_alter_productimage_options"),
]
operations = [
migrations.RemoveField(
model_name="productimage",
name="visible",
),
migrations.RemoveField(
model_name="productimage",
name="is_dupe",
),
]
}}}
And now the migration runs as expected.
There might be some other subtle errors around GeneratedField and
migrations, but this is the most obvious one I could reproduce.
--
Ticket URL: <
https://code.djangoproject.com/ticket/36545>
Django <
https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.