diff --git a/aiohttp_admin/backends/sqlalchemy.py b/aiohttp_admin/backends/sqlalchemy.py index d00f453d..a9093646 100644 --- a/aiohttp_admin/backends/sqlalchemy.py +++ b/aiohttp_admin/backends/sqlalchemy.py @@ -177,15 +177,24 @@ def __init__(self, db: AsyncEngine, model_or_table: Union[sa.Table, type[Declara "target": key.column.name} else: field, inp, props = get_components(c.type) - props["source"] = c.name + props["source"] = c.name if isinstance(c.type, sa.Enum): props["choices"] = tuple({"id": e.value, "name": e.name} for e in c.type.python_type) + length = getattr(c.type, "length", 0) + if length is None or length > 31: + props["fullWidth"] = True + if length is None or length > 127: + props["multiline"] = True + if isinstance(c.default, sa.ColumnDefault): props["placeholder"] = c.default.arg + if c.comment: + props["helperText"] = c.comment + self.fields[c.name] = comp(field, props) if c.computed is None: # TODO: Allow custom props (e.g. disabled, multiline, rows etc.) diff --git a/tests/test_backends_sqlalchemy.py b/tests/test_backends_sqlalchemy.py index ea196880..e3726646 100644 --- a/tests/test_backends_sqlalchemy.py +++ b/tests/test_backends_sqlalchemy.py @@ -35,12 +35,15 @@ class TestModel(base): # type: ignore[misc,valid-type] assert r.name == "dummy" assert r.primary_key == "id" assert r.fields == {"id": comp("NumberField", {"source": "id"}), - "num": comp("TextField", {"source": "num"})} + "num": comp("TextField", {"source": "num", "fullWidth": True, + "multiline": True})} # Autoincremented PK should not be in create form assert r.inputs == { "id": comp("NumberInput", {"source": "id", "validate": [func("required", ())]}) | {"show_create": False}, - "num": comp("TextInput", {"source": "num", "validate": [func("required", ())]}) + "num": comp("TextInput", { + "source": "num", "fullWidth": True, "multiline": True, + "validate": [func("required", ())]}) | {"show_create": True} } @@ -66,6 +69,21 @@ def test_table(mock_engine: AsyncEngine) -> None: } +def test_extra_props(base: type[DeclarativeBase], mock_engine: AsyncEngine) -> None: + class TestModel(base): # type: ignore[misc,valid-type] + __tablename__ = "dummy" + id: Mapped[int] = mapped_column(primary_key=True) + num: Mapped[str] = mapped_column(sa.String(128), comment="Foo", default="Bar") + + r = SAResource(mock_engine, TestModel) + assert r.fields["num"]["props"] == { + "source": "num", "fullWidth": True, "multiline": True, "placeholder": "Bar", + "helperText": "Foo"} + assert r.inputs["num"]["props"] == { + "source": "num", "fullWidth": True, "multiline": True, "placeholder": "Bar", + "helperText": "Foo", "validate": [func("maxLength", (128,))]} + + async def test_binary( base: DeclarativeBase, aiohttp_client: Callable[[web.Application], Awaitable[TestClient]], login: _Login @@ -243,19 +261,21 @@ async def test_nonid_pk(base: type[DeclarativeBase], mock_engine: AsyncEngine) - class TestModel(base): # type: ignore[misc,valid-type] __tablename__ = "test" num: Mapped[int] = mapped_column(primary_key=True) - other: Mapped[str] + other: Mapped[str] = mapped_column(sa.String(64)) r = SAResource(mock_engine, TestModel) assert r.name == "test" assert r.primary_key == "num" assert r.fields == { "num": comp("NumberField", {"source": "num"}), - "other": comp("TextField", {"source": "other"}) + "other": comp("TextField", {"source": "other", "fullWidth": True}) } assert r.inputs == { "num": comp("NumberInput", {"source": "num", "validate": [func("required", ())]}) | {"show_create": False}, - "other": comp("TextInput", {"source": "other", "validate": [func("required", ())]}) + "other": comp("TextInput", { + "fullWidth": True, "source": "other", + "validate": [func("required", ()), func("maxLength", (64,))]}) | {"show_create": True} }