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

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion visit 
https://groups.google.com/d/msgid/django-updates/010701988a929421-66ba10f3-9b4d-4197-9e16-60d92bfd0bec-000000%40eu-central-1.amazonses.com.

Reply via email to