Opened 77 minutes ago

Last modified 60 minutes ago

#37060 assigned Bug

AlterField doesn't propagate type changes through transitive attname-based to_field references

Reported by: Andrea Zanotto Owned by: Andrea Zanotto
Component: Migrations Version: 5.2
Severity: Normal Keywords: schema alterfield to_field attname foreignkey
Cc: Triage Stage: Unreviewed
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

When a relation uses an attname-based to_field such as primary_id, Django accepts and resolves it correctly, but AlterField does not always propagate type changes through transitive dependencies.

Minimal example:

    class Primary(models.Model):
        code = models.CharField(max_length=5, unique=True)

    class Related(models.Model):
        primary = models.OneToOneField(
            Primary,
            to_field="code",
            on_delete=models.CASCADE,
        )

    class Dependent(models.Model):
        related = models.ForeignKey(
            Related,
            to_field="primary_id",
            on_delete=models.CASCADE,
        )

If Primary.code.max_length is changed from 5 to 11, Django correctly updates Related.primary_id, but Dependent.related_id remains at varchar(5) instead of widening to varchar(11).

Behavior

Using:

  • to_field="primary" works correctly
  • to_field="primary_id" does not

However, Django currently accepts primary_id as a valid to_field reference because relation resolution uses Options.get_field(), and get_field() maps both the field name and the relation attname to the same field object.
That means if ForeignKey(..., to_field="primary_id") is accepted and resolved as valid, schema alteration should handle it the same way as to_field="primary".

Cause

The schema dependency walk in django/db/backends/base/schema.py uses _related_non_m2m_objects() and _is_relevant_relation() to discover fields whose database type must be updated.
Before the fix, _is_relevant_relation() compared only the altered field's name against field.to_fields.
In the example above, the recursive step compares:

  • altered field name: primary
  • dependent field to_fields: ["primary_id"]

So the dependency is missed even though both names refer to the same remote field.
As a result, the transitive dependent column is excluded from schema propagation.

Expected behavior

Altering Primary.code from max_length=5 to max_length=11 should also widen:

  • Related.primary_id
  • Dependent.related_id

regardless of whether the dependent relation was declared with:

  • to_field="primary"
  • to_field="primary_id"

Fix

Treat attname-based to_field values as equivalent to the actual remote field when determining whether a relation depends on the altered field.
In practice, this means resolving each to_field through remote_model._meta.get_field(...) and comparing field identity, instead of only comparing raw field names.
SQLite also needs the same recursive dependency logic in its schema editor override when rebuilding related tables after altering unique fields.

Change History (1)

comment:1 by Andrea Zanotto, 60 minutes ago

Owner: set to Andrea Zanotto
Status: newassigned
Note: See TracTickets for help on using tickets.
Back to Top