#36545: On Postgres, removing a generated field and its dependent field 
generates
an invalid migration with makemigrations
-----------------------------+--------------------------------------
     Reporter:  john-parton  |                    Owner:  (none)
         Type:  Bug          |                   Status:  new
    Component:  Migrations   |                  Version:  5.2
     Severity:  Normal       |               Resolution:
     Keywords:               |             Triage Stage:  Unreviewed
    Has patch:  0            |      Needs documentation:  0
  Needs tests:  0            |  Patch needs improvement:  0
Easy pickings:  0            |                    UI/UX:  0
-----------------------------+--------------------------------------
Description changed by john-parton:

Old description:

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

New description:

 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.

 Could not find a duplicate:
 
https://code.djangoproject.com/query?description=~generated+field+migration&status=assigned&status=closed&status=new&order=id&desc=1&col=id&col=summary&col=owner&col=type&col=component

--
-- 
Ticket URL: <https://code.djangoproject.com/ticket/36545#comment:1>
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 django-updates+unsubscr...@googlegroups.com.
To view this discussion visit 
https://groups.google.com/d/msgid/django-updates/010701988a93a671-1abe315f-7c34-414e-bcb7-e492f7458b31-000000%40eu-central-1.amazonses.com.

Reply via email to