diff --git a/src/python-fastui/fastui/components/display.py b/src/python-fastui/fastui/components/display.py index 5f147def..794dddb1 100644 --- a/src/python-fastui/fastui/components/display.py +++ b/src/python-fastui/fastui/components/display.py @@ -79,14 +79,14 @@ class Details(BaseModel, extra='forbid'): @pydantic.model_validator(mode='after') def _fill_fields(self) -> _te.Self: + fields = {**self.data.model_fields, **self.data.model_computed_fields} + if self.fields is None: - self.fields = [ - DisplayLookup(field=name, title=field.title) for name, field in self.data.model_fields.items() - ] + self.fields = [DisplayLookup(field=name, title=field.title) for name, field in fields.items()] else: # add pydantic titles to fields that don't have them for field in (c for c in self.fields if c.title is None): - pydantic_field = self.data.model_fields.get(field.field) + pydantic_field = fields.get(field.field) if pydantic_field and pydantic_field.title: field.title = pydantic_field.title return self diff --git a/src/python-fastui/tests/test_tables_display.py b/src/python-fastui/tests/test_tables_display.py index c1c2274b..923a4fc2 100644 --- a/src/python-fastui/tests/test_tables_display.py +++ b/src/python-fastui/tests/test_tables_display.py @@ -86,7 +86,11 @@ def test_display_no_fields(): # insert_assert(d.model_dump(by_alias=True, exclude_none=True)) assert d.model_dump(by_alias=True, exclude_none=True) == { 'data': {'id': 1, 'name': 'john', 'representation': '1: john'}, - 'fields': [{'field': 'id'}, {'title': 'Name', 'field': 'name'}], + 'fields': [ + {'field': 'id'}, + {'title': 'Name', 'field': 'name'}, + {'title': 'Representation', 'field': 'representation'}, + ], 'type': 'Details', } @@ -102,3 +106,41 @@ def test_display_fields(): 'fields': [{'title': 'ID', 'field': 'id'}, {'title': 'Name', 'field': 'name'}], 'type': 'Details', } + + +def test_table_respect_computed_field_title(): + class Foo(BaseModel): + id: int + + @computed_field(title='Foo Name') + def name(self) -> str: + return f'foo{self.id}' + + foos = [Foo(id=1)] + table = components.Table(data=foos) + + # insert_assert(table.model_dump(by_alias=True, exclude_none=True)) + assert table.model_dump(by_alias=True, exclude_none=True) == { + 'data': [{'id': 1, 'name': 'foo1'}], + 'columns': [{'field': 'id'}, {'title': 'Foo Name', 'field': 'name'}], + 'type': 'Table', + } + + +def test_details_respect_computed_field_title(): + class Foo(BaseModel): + id: int + + @computed_field(title='Foo Name') + def name(self) -> str: + return f'foo{self.id}' + + foos = Foo(id=1) + details = components.Details(data=foos) + + # insert_assert(table.model_dump(by_alias=True, exclude_none=True)) + assert details.model_dump(by_alias=True, exclude_none=True) == { + 'data': {'id': 1, 'name': 'foo1'}, + 'fields': [{'field': 'id'}, {'title': 'Foo Name', 'field': 'name'}], + 'type': 'Details', + }