Skip to content

Commit a470421

Browse files
committed
Fix error with index on textfield, clear error for models
1 parent 691fe46 commit a470421

7 files changed

Lines changed: 164 additions & 40 deletions

File tree

CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
The format is based on [Keep a Changelog](https://keepachangelog.com/).
6+
7+
## 0.1.1
8+
9+
### Fixed
10+
11+
- Model creation errors now include the Django model name in the message
12+
(e.g. `Failed to create Tortoise model 'ArticleTortoise' for Django model 'blog.article': …`)
13+
instead of only showing the raw Tortoise exception.
14+
- Stopped mapping `unique` and `db_index` attributes to generated Tortoise
15+
fields. The library relies on Django's existing database schema, so
16+
Tortoise-level index declarations are unnecessary and caused
17+
`ConfigurationError` for field types that reject them (e.g. `TextField`
18+
with `unique=True`).

django_tortoise/code_generator.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,13 @@ def _common_kwargs_source(field_info: FieldInfo) -> dict[str, str]:
6363
6464
Mirrors ``fields._common_kwargs()`` but returns source-code strings
6565
instead of runtime values.
66+
67+
Note: ``unique`` and ``db_index`` are intentionally NOT emitted.
68+
The library relies on the existing Django-managed database schema.
6669
"""
6770
kwargs: dict[str, str] = {}
6871
if field_info.null:
6972
kwargs["null"] = "True"
70-
if field_info.unique:
71-
kwargs["unique"] = "True"
72-
if field_info.db_index:
73-
kwargs["db_index"] = "True"
7473
if field_info.primary_key:
7574
kwargs["primary_key"] = "True"
7675
# source_field when column differs from name

django_tortoise/fields.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,16 +40,17 @@ def _common_kwargs(field_info: FieldInfo) -> dict[str, Any]:
4040
"""
4141
Extract common kwargs shared by most Tortoise fields.
4242
43-
Maps null, unique, db_index, primary_key, source_field (when DB column
43+
Maps null, primary_key, source_field (when DB column
4444
differs from field name), and default values.
45+
46+
Note: ``unique`` and ``db_index`` are intentionally NOT mapped.
47+
The library relies on the existing Django-managed database schema,
48+
so Tortoise-level index/unique declarations are unnecessary and can
49+
cause errors (e.g. ``TextField`` does not support ``unique`` in Tortoise).
4550
"""
4651
kwargs: dict[str, Any] = {}
4752
if field_info.null:
4853
kwargs["null"] = True
49-
if field_info.unique:
50-
kwargs["unique"] = True
51-
if field_info.db_index:
52-
kwargs["db_index"] = True
5354
if field_info.primary_key:
5455
kwargs["primary_key"] = True
5556
# Map db_column -> source_field

django_tortoise/generator.py

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,20 @@ def generate_tortoise_model(
5757
meta_class = _build_meta_class(model_info, tortoise_fields, tortoise_app_name)
5858
class_name = f"{model_info.model_class.__name__}Tortoise"
5959

60-
tortoise_model = type(
61-
class_name,
62-
(tortoise_models.Model,),
63-
{
64-
"Meta": meta_class,
65-
**tortoise_fields,
66-
},
67-
)
60+
try:
61+
tortoise_model = type(
62+
class_name,
63+
(tortoise_models.Model,),
64+
{
65+
"Meta": meta_class,
66+
**tortoise_fields,
67+
},
68+
)
69+
except Exception as exc:
70+
raise type(exc)(
71+
f"Failed to create Tortoise model '{class_name}' for Django model "
72+
f"'{model_info.app_label}.{model_info.model_name}': {exc}"
73+
) from exc
6874

6975
logger.debug(
7076
"Generated Tortoise model '%s' for Django model '%s.%s' (table: %s, fields: %d)",
@@ -127,14 +133,20 @@ def generate_tortoise_model_full(
127133
meta_class = _build_meta_class(model_info, tortoise_fields, tortoise_app_name)
128134
class_name = f"{model_info.model_class.__name__}Tortoise"
129135

130-
tortoise_model = type(
131-
class_name,
132-
(tortoise_models.Model,),
133-
{
134-
"Meta": meta_class,
135-
**tortoise_fields,
136-
},
137-
)
136+
try:
137+
tortoise_model = type(
138+
class_name,
139+
(tortoise_models.Model,),
140+
{
141+
"Meta": meta_class,
142+
**tortoise_fields,
143+
},
144+
)
145+
except Exception as exc:
146+
raise type(exc)(
147+
f"Failed to create Tortoise model '{class_name}' for Django model "
148+
f"'{model_info.app_label}.{model_info.model_name}': {exc}"
149+
) from exc
138150

139151
logger.debug(
140152
"Generated Tortoise model '%s' for Django model '%s.%s' "

tests/test_code_generator.py

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -98,20 +98,30 @@ class _DummyComment:
9898
class TestCommonKwargsSource:
9999
"""Tests for _common_kwargs_source."""
100100

101-
def test_null_and_unique(self):
102-
"""TC-1.1: produces correct kwargs for null, unique."""
103-
info = _make_field_info(null=True, unique=True)
101+
def test_null_kwarg(self):
102+
"""TC-1.1: produces correct kwargs for null."""
103+
info = _make_field_info(null=True)
104104
result = _common_kwargs_source(info)
105105
assert result["null"] == "True"
106-
assert result["unique"] == "True"
107106

108-
def test_db_index_and_primary_key(self):
109-
"""TC-1.1: produces correct kwargs for db_index, primary_key."""
110-
info = _make_field_info(db_index=True, primary_key=True)
107+
def test_unique_not_in_output(self):
108+
"""unique is not included in kwargs source (DB schema handles it)."""
109+
info = _make_field_info(unique=True)
110+
result = _common_kwargs_source(info)
111+
assert "unique" not in result
112+
113+
def test_primary_key_kwarg(self):
114+
"""TC-1.1: produces correct kwargs for primary_key."""
115+
info = _make_field_info(primary_key=True)
111116
result = _common_kwargs_source(info)
112-
assert result["db_index"] == "True"
113117
assert result["primary_key"] == "True"
114118

119+
def test_db_index_not_in_output(self):
120+
"""db_index is not included in kwargs source (DB schema handles it)."""
121+
info = _make_field_info(db_index=True)
122+
result = _common_kwargs_source(info)
123+
assert "db_index" not in result
124+
115125
def test_simple_literal_default_int(self):
116126
"""TC-1.2: handles int default."""
117127
info = _make_field_info(has_default=True, default=42)

tests/test_fields.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -277,16 +277,17 @@ def test_null_field(self):
277277
result = convert_field(info)
278278
assert result.null is True
279279

280-
def test_unique_field(self):
280+
def test_unique_not_mapped(self):
281+
"""unique is not mapped to Tortoise (DB schema handles it)."""
281282
info = _make_field_info(internal_type="IntegerField", unique=True)
282283
result = convert_field(info)
283-
assert result.unique is True
284+
assert result.unique is False
284285

285-
def test_db_index_field(self):
286+
def test_db_index_not_mapped(self):
287+
"""db_index is not mapped to Tortoise (DB schema handles it)."""
286288
info = _make_field_info(internal_type="IntegerField", db_index=True)
287289
result = convert_field(info)
288-
# Tortoise stores db_index as 'index' on the field instance
289-
assert result.index is True
290+
assert result.index is False
290291

291292
def test_source_field_mapping(self):
292293
info = _make_field_info(

tests/test_generator.py

Lines changed: 85 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,45 @@
55
classes from introspected Django model metadata.
66
"""
77

8+
import pytest
89
from tortoise import models as tortoise_models
910

10-
from django_tortoise.generator import generate_tortoise_model
11-
from django_tortoise.introspection import ModelInfo, introspect_model
11+
from django_tortoise import generator
12+
from django_tortoise.generator import generate_tortoise_model, generate_tortoise_model_full
13+
from django_tortoise.introspection import FieldInfo, ModelInfo, introspect_model
14+
15+
16+
def _make_field_info(**overrides) -> FieldInfo:
17+
"""Helper to create FieldInfo with sensible defaults."""
18+
defaults = dict(
19+
name="test_field",
20+
internal_type="CharField",
21+
column="test_field",
22+
primary_key=False,
23+
null=False,
24+
unique=False,
25+
has_default=False,
26+
default=None,
27+
max_length=100,
28+
max_digits=None,
29+
decimal_places=None,
30+
db_index=False,
31+
choices=None,
32+
enum_type=None,
33+
is_relation=False,
34+
related_model=None,
35+
related_model_label=None,
36+
on_delete=None,
37+
related_name=None,
38+
is_self_referential=False,
39+
many_to_many=False,
40+
through_model=None,
41+
through_db_table=None,
42+
is_auto_field=False,
43+
django_field=None,
44+
)
45+
defaults.update(overrides)
46+
return FieldInfo(**defaults)
1247

1348

1449
class TestGenerateBasicModel:
@@ -256,3 +291,51 @@ def test_unique_together_with_all_data_fields(self):
256291
assert tortoise_model is not None
257292
assert hasattr(tortoise_model.Meta, "unique_together")
258293
assert ("a", "b") in tortoise_model.Meta.unique_together
294+
295+
296+
class TestModelCreationErrorContext:
297+
"""Errors during model creation include model/field context."""
298+
299+
@pytest.fixture()
300+
def failing_model_info(self):
301+
fi = _make_field_info(
302+
name="id",
303+
internal_type="AutoField",
304+
column="id",
305+
primary_key=True,
306+
is_auto_field=True,
307+
max_length=None,
308+
)
309+
return ModelInfo(
310+
model_class=type("MyModel", (), {}),
311+
app_label="myapp",
312+
model_name="mymodel",
313+
db_table="myapp_mymodel",
314+
fields=[fi],
315+
unique_together=[],
316+
is_abstract=False,
317+
is_proxy=False,
318+
is_managed=True,
319+
pk_name="id",
320+
)
321+
322+
@pytest.fixture()
323+
def patch_failing_model(self, monkeypatch):
324+
"""Replace tortoise_models.Model with a class whose metaclass raises."""
325+
326+
class FailingModel:
327+
def __init_subclass__(cls, **kwargs):
328+
raise RuntimeError("simulated metaclass error")
329+
330+
fake_module = type("FakeModule", (), {"Model": FailingModel})()
331+
monkeypatch.setattr(generator, "tortoise_models", fake_module)
332+
333+
def test_error_includes_model_context(self, failing_model_info, patch_failing_model):
334+
"""When type() raises during model creation, the error includes model context."""
335+
with pytest.raises(RuntimeError, match=r"myapp\.mymodel"):
336+
generate_tortoise_model(failing_model_info)
337+
338+
def test_error_includes_model_context_full(self, failing_model_info, patch_failing_model):
339+
"""generate_tortoise_model_full() also includes model context on error."""
340+
with pytest.raises(RuntimeError, match=r"myapp\.mymodel"):
341+
generate_tortoise_model_full(failing_model_info)

0 commit comments

Comments
 (0)