Skip to content

fix(migrations): migration serialization bugs for PK fields#2195

Open
seladb wants to merge 2 commits into
tortoise:developfrom
seladb:bugfix-2176
Open

fix(migrations): migration serialization bugs for PK fields#2195
seladb wants to merge 2 commits into
tortoise:developfrom
seladb:bugfix-2176

Conversation

@seladb
Copy link
Copy Markdown
Contributor

@seladb seladb commented May 15, 2026

Description

Fixes 2 bugs that were discovered in #2176

Motivation and Context

Two bugs in migration serialization when a PK field is configured with
non-default attributes:

  1. generated=False on PK fields was lost during serialization
    Field.deconstruct() only emitted generated when the value was
    truthy. If a PK field explicitly set generated=False, the kwarg was
    omitted, causing the field to revert to its per-engine default
    (True for IntField) on deserialization. The fix includes the
    generated kwarg whenever self.pk is true, preserving the explicit
    value.
  2. PK fields with source_field="<FIELD_NAME>" were dropped from CreateModel
    MigrationWriter._format_create_model() collected all source_field
    values and skipped any field whose name appeared in that set. When a
    PK field had source_field="<FIELD_NAME>", its own name matched the set entry
    and it was incorrectly excluded from the migration output. The fix
    exempts PK fields from this deduplication logic.

How Has This Been Tested?

  • Added tests for deconstructing with different options of generated in PK and non-PK fields
  • Added test for a migration with source_field that is similar to the field name

Checklist:

  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I have updated the documentation accordingly.
  • I have added the changelog accordingly.
  • I have read the CONTRIBUTING document.
  • I have added tests to cover my changes.
  • All new and existing tests passed.

seladb added 2 commits May 15, 2026 00:32
- Preserve `generated=False` on PK fields in deconstruct() output
- Do not skip PK fields with `source_field="id"` in CreateModel migration
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented May 15, 2026

Merging this PR will not alter performance

✅ 24 untouched benchmarks


Comparing seladb:bugfix-2176 (1e5401a) with develop (c0de49f)

Open in CodSpeed

@seladb seladb changed the title Fix migration serialization bugs for PK fields fix(migration): migration serialization bugs for PK fields May 15, 2026
@seladb seladb changed the title fix(migration): migration serialization bugs for PK fields fix(migrations): migration serialization bugs for PK fields May 15, 2026
@seladb
Copy link
Copy Markdown
Contributor Author

seladb commented May 16, 2026

@waketzheng @abondar I'm not sure why the coverage workflow didn't start, can you cancel and restart it (I don't have permissions to do so)?

@seladb
Copy link
Copy Markdown
Contributor Author

seladb commented May 27, 2026

@abondar @waketzheng can you please review this PR?

@waketzheng
Copy link
Copy Markdown
Contributor

@seladb Thanks for tracking this down. The fixes make sense for the two cases discussed in #2176: preserving generated=False on primary-key fields, and avoiding the missing field in CreateModel when a PK has source_field equal to its own field name.

One thing worth considering is whether the CreateModel fix should be expressed in terms of “source fields claimed by another field” rather than special-casing PK fields.

The current change fixes the reported PK case:

if name in source_fields and not getattr(field, "pk", False):
    continue

But the underlying issue seems a bit broader: a field should only be skipped when its name is the backing DB column for another field, such as the generated author_id field behind a ForeignKeyField. If a field has source_field equal to its own name, that is redundant but it probably should not cause the field to be dropped, regardless of whether it is a PK.

A slightly more general approach might be:

source_fields = {
    field.source_field
    for name, field in operation.fields
    if (
        field is not None
        and getattr(field, "source_field", None)
        and field.source_field != name
    )
}

Then the existing if name in source_fields: continue logic would still skip FK backing fields, while keeping both PK and non-PK fields whose source_field is simply the same as the model field name.

It may also be worth adding one regression test for the non-PK version of the same pattern, for example:

("name", fields.CharField(max_length=100, source_field="name"))

The generated=False deconstruction tests look useful. Since #2176 is about repeated migration generation, a higher-level regression test that exercises the writer/autodetector path for primary_key=True, generated=False could make the fix a bit more future-proof too.

Overall this looks like the right direction; the main question is whether the source_field filtering should be generalized so the same bug does not remain for non-PK fields.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants