Opened 6 weeks ago

Last modified 6 weeks ago

#36545 closed Bug

On Postgres, removing a generated field and its dependent field generates an invalid migration with makemigrations — at Version 1

Reported by: john-parton Owned by:
Component: Migrations Version: 5.2
Severity: Normal Keywords:
Cc: Triage Stage: Unreviewed
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description (last modified by john-parton)

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

Change History (1)

comment:1 by john-parton, 6 weeks ago

Description: modified (diff)
Note: See TracTickets for help on using tickets.
Back to Top