Opened 2 years ago

Last modified 2 years ago

#25817 new Bug

Unable to rename a field reference in foreign key 'to_field'

Reported by: Simon Kelly Owned by: nobody
Component: Migrations Version: master
Severity: Normal Keywords: to_field rename
Cc: Simon Charette Triage Stage: Accepted
Has patch: no Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Steps to reproduce:

  1. Create a model with a foreign key referencing another model's field via the 'to_field' arg.
  2. Generate the initial migration
class Bar(models.Model):
    bar_id = models.CharField(max_length=255, db_index=True, unique=True)

class Bazz(models.Model):
    bar = models.ForeignKey(Bar, to_field='bar_id')
  1. Rename the field referenced in 'to_field' and create a migration for the change

Rename 'bar_id' to 'external_id':

class Bar(models.Model):
    external_id = models.CharField(max_length=255, db_index=True, unique=True)

class Bazz(models.Model):
    bar = models.ForeignKey(Bar, to_field='external_id') 

Migration:

    operations = [
        migrations.RenameField(
            model_name='bar',
            old_name='bar_id',
            new_name='external_id',
        ),
        migrations.AlterField(
            model_name='bazz',
            name='bar',
            field=models.ForeignKey(to='form_processor.Bar', to_field=b'external_id'),
            preserve_default=True,
        ),
    ]
  1. Run the migration

Error:

Traceback (most recent call last):
  File "./manage.py", line 73, in <module>
    execute_from_command_line(sys.argv)
  File "/home/skelly/.virtualenvs/hq/local/lib/python2.7/site-packages/django/core/management/__init__.py", line 385, in execute_from_command_line
    utility.execute()
  File "/home/skelly/.virtualenvs/hq/local/lib/python2.7/site-packages/django/core/management/__init__.py", line 377, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/home/skelly/.virtualenvs/hq/local/lib/python2.7/site-packages/django/core/management/base.py", line 288, in run_from_argv
    self.execute(*args, **options.__dict__)
  File "/home/skelly/.virtualenvs/hq/local/lib/python2.7/site-packages/django/core/management/commands/sqlmigrate.py", line 30, in execute
    return super(Command, self).execute(*args, **options)
  File "/home/skelly/.virtualenvs/hq/local/lib/python2.7/site-packages/django/core/management/base.py", line 338, in execute
    output = self.handle(*args, **options)
  File "/home/skelly/.virtualenvs/hq/local/lib/python2.7/site-packages/django/core/management/commands/sqlmigrate.py", line 61, in handle
    sql_statements = executor.collect_sql(plan)
  File "/home/skelly/.virtualenvs/hq/local/lib/python2.7/site-packages/django/db/migrations/executor.py", line 82, in collect_sql
    migration.apply(project_state, schema_editor, collect_sql=True)
  File "/home/skelly/.virtualenvs/hq/local/lib/python2.7/site-packages/django/db/migrations/migration.py", line 108, in apply
    operation.database_forwards(self.app_label, schema_editor, project_state, new_state)
  File "/home/skelly/.virtualenvs/hq/local/lib/python2.7/site-packages/django/db/migrations/operations/fields.py", line 139, in database_forwards
    schema_editor.alter_field(from_model, from_field, to_field)
  File "/home/skelly/.virtualenvs/hq/local/lib/python2.7/site-packages/django/db/backends/schema.py", line 445, in alter_field
    old_db_params = old_field.db_parameters(connection=self.connection)
  File "/home/skelly/.virtualenvs/hq/local/lib/python2.7/site-packages/django/db/models/fields/related.py", line 1787, in db_parameters
    return {"type": self.db_type(connection), "check": []}
  File "/home/skelly/.virtualenvs/hq/local/lib/python2.7/site-packages/django/db/models/fields/related.py", line 1778, in db_type
    rel_field = self.related_field
  File "/home/skelly/.virtualenvs/hq/local/lib/python2.7/site-packages/django/db/models/fields/related.py", line 1684, in related_field
    return self.foreign_related_fields[0]
  File "/home/skelly/.virtualenvs/hq/local/lib/python2.7/site-packages/django/db/models/fields/related.py", line 1442, in foreign_related_fields
    return tuple(rhs_field for lhs_field, rhs_field in self.related_fields)
  File "/home/skelly/.virtualenvs/hq/local/lib/python2.7/site-packages/django/db/models/fields/related.py", line 1429, in related_fields
    self._related_fields = self.resolve_related_fields()
  File "/home/skelly/.virtualenvs/hq/local/lib/python2.7/site-packages/django/db/models/fields/related.py", line 1422, in resolve_related_fields
    else self.rel.to._meta.get_field_by_name(to_field_name)[0])
  File "/home/skelly/.virtualenvs/hq/local/lib/python2.7/site-packages/django/db/models/options.py", line 420, in get_field_by_name
    % (self.object_name, name))
django.db.models.fields.FieldDoesNotExist: Bar has no field named 'bar_id'

Change History (5)

comment:1 Changed 2 years ago by Simon Kelly

As a workaround I was able to use the following custom migration:

    operations = [
        migrations.RenameField(
            model_name='bar',
            old_name='bar_id',
            new_name='external_id',
        ),
        migrations.RunSQL(
            'SELECT 1',
            'SELECT 1',
            state_operations=[
                migrations.AlterField(
                    model_name='bazz',
                    name='bar',
                    field=models.ForeignKey(to='form_processor.Bar', to_field=b'external_id'),
                    preserve_default=True,
                ),
            ]
        )
    ]

However this migration is un-sqaushable:

$ ./manage.py squashmigrations form_processor 0002
Will squash the following migrations:
 - 0001_initial
 - 0002_rename_bar_id_to_external_id
Do you wish to proceed? [yN] y
Optimizing...
Traceback (most recent call last):
  File "./manage.py", line 73, in <module>
    execute_from_command_line(sys.argv)
  File "/home/skelly/.virtualenvs/hq/local/lib/python2.7/site-packages/django/core/management/__init__.py", line 385, in execute_from_command_line
    utility.execute()
  File "/home/skelly/.virtualenvs/hq/local/lib/python2.7/site-packages/django/core/management/__init__.py", line 377, in execute
    self.fetch_command(subcommand).run_from_argv(self.argv)
  File "/home/skelly/.virtualenvs/hq/local/lib/python2.7/site-packages/django/core/management/base.py", line 288, in run_from_argv
    self.execute(*args, **options.__dict__)
  File "/home/skelly/.virtualenvs/hq/local/lib/python2.7/site-packages/django/core/management/base.py", line 338, in execute
    output = self.handle(*args, **options)
  File "/home/skelly/.virtualenvs/hq/local/lib/python2.7/site-packages/django/core/management/commands/squashmigrations.py", line 130, in handle
    fh.write(writer.as_string())
  File "/home/skelly/.virtualenvs/hq/local/lib/python2.7/site-packages/django/db/migrations/writer.py", line 131, in as_string
    operation_string, operation_imports = OperationWriter(operation).serialize()
  File "/home/skelly/.virtualenvs/hq/local/lib/python2.7/site-packages/django/db/migrations/writer.py", line 88, in serialize
    arg_string, arg_imports = MigrationWriter.serialize(arg_value)
  File "/home/skelly/.virtualenvs/hq/local/lib/python2.7/site-packages/django/db/migrations/writer.py", line 263, in serialize
    item_string, item_imports = cls.serialize(item)
  File "/home/skelly/.virtualenvs/hq/local/lib/python2.7/site-packages/django/db/migrations/writer.py", line 350, in serialize
    return cls.serialize_deconstructed(*value.deconstruct())
  File "/home/skelly/.virtualenvs/hq/local/lib/python2.7/site-packages/django/db/migrations/writer.py", line 226, in serialize_deconstructed
    module, name = path.rsplit(".", 1)
ValueError: need more than 1 value to unpack
Last edited 2 years ago by Simon Kelly (previous) (diff)

comment:2 Changed 2 years ago by Simon Kelly

Type: UncategorizedBug

comment:3 Changed 2 years ago by Simon Charette

Triage Stage: UnreviewedAccepted
Version: 1.7master

The list of operations generated should look like:

  1. Alter the field Bazz.bar type to an IntegerField in order to drop the FK constraint.
  2. Perform the Bar.bar_id rename to external_id;
  3. Alter back the field Bazz.bar to a FK referring to Bar.external_id;

@snopoke, can you confirm these operations work?

comment:4 Changed 2 years ago by Simon Charette

Cc: Simon Charette added

comment:5 in reply to:  4 Changed 2 years ago by Simon Kelly

Replying to charettes:

Yes that does work and I guess its fine for small tables but not great for a big table. Certainly solves my problem right now. Thanks.

Note: See TracTickets for help on using tickets.
Back to Top