From bc4045abd99936d70bd115ea37744dd3521afce0 Mon Sep 17 00:00:00 2001 From: xync Date: Thu, 21 May 2026 01:02:31 +0300 Subject: [PATCH] Fix db_default on ForeignKey/OneToOne dropped during _init_relations When db_default is set on a ForeignKeyField/OneToOneField, the value never reaches the generated DDL: `_init_relations` builds the implicit `_id` key field by copying the related model's PK and patching a small set of attributes from the FK object, but `db_default` is not in that list. The schema editor then iterates fields_db_projection (which contains the key field) and finds has_db_default() == False, so the CREATE TABLE for the owning model has no DEFAULT clause on the FK column. Add `db_default` to the attribute-copy tuple so it propagates from the FK to the underlying column, mirroring how PR #2129 fixed the same omission for non-relation fields. Fixes #2199 --- CHANGELOG.rst | 1 + tests/migrations/test_schema_editor_sql.py | 33 ++++++++++++++++++++++ tortoise/apps.py | 2 +- 3 files changed, 35 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b111c9525..a6fa2cfae 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -21,6 +21,7 @@ Added Fixed ^^^^^ - ``MigrationRecorder`` now uses parameterized queries; fixes MariaDB/MySQL rejecting ISO-8601 ``applied_at`` values. (#2132) +- ``db_default`` on ``ForeignKeyField``/``OneToOneField`` now propagates to the underlying ``_id`` column, so ``CREATE TABLE`` emits the ``DEFAULT`` clause for FK columns. (#2199) 1.1.7 ----- diff --git a/tests/migrations/test_schema_editor_sql.py b/tests/migrations/test_schema_editor_sql.py index ee9ca8860..30578f5f8 100644 --- a/tests/migrations/test_schema_editor_sql.py +++ b/tests/migrations/test_schema_editor_sql.py @@ -249,3 +249,36 @@ class Meta: sql = client.executed[0] assert 'CREATE TABLE "widget"' in sql assert "DEFAULT 'active'" in sql + + +@pytest.mark.asyncio +async def test_create_model_includes_db_default_on_fk() -> None: + """CreateModel should include DEFAULT clause for FK columns with db_default.""" + + class Dc(Model): + id = fields.IntField(pk=True) + + class Meta: + table = "dc" + app = "models" + + class App(Model): + id = fields.IntField(pk=True) + dc = fields.ForeignKeyField("models.Dc", db_default=2) + + class Meta: + table = "app" + app = "models" + + init_apps(Dc, App) + + client = FakeClient("sql") + editor = TestSchemaEditor(client) + + await editor.create_model(App) + + assert len(client.executed) == 1 + sql = client.executed[0] + assert 'CREATE TABLE "app"' in sql + assert '"dc_id"' in sql + assert "DEFAULT 2" in sql diff --git a/tortoise/apps.py b/tortoise/apps.py index f2be4800f..637ba142b 100644 --- a/tortoise/apps.py +++ b/tortoise/apps.py @@ -192,7 +192,7 @@ def init_fk_o2o_field(model: type[Model], field: str, is_o2o: bool = False) -> N key_field = f"{field}_id" key_fk_object.reference = fk_object key_fk_object.source_field = fk_object.source_field or key_field - for attr in ("index", "default", "null", "generated", "description"): + for attr in ("index", "default", "null", "generated", "description", "db_default"): setattr(key_fk_object, attr, getattr(fk_object, attr)) if is_o2o: key_fk_object.pk = fk_object.pk