diff --git a/docs/module/crud.md b/docs/module/crud.md index e83ce32..9dc457c 100644 --- a/docs/module/crud.md +++ b/docs/module/crud.md @@ -212,6 +212,9 @@ Two search strategies are available, both compatible with [`offset_paginate`](.. ### Full-text search +!!! info "Added in `v2.2.1`" + The model's primary key is always included in `searchable_fields` automatically, so searching by ID works out of the box without any configuration. When no `searchable_fields` are declared, only the primary key is searched. + Declare `searchable_fields` on the CRUD class. Relationship traversal is supported via tuples: ```python diff --git a/src/fastapi_toolsets/crud/factory.py b/src/fastapi_toolsets/crud/factory.py index 7dafe6a..6a87c79 100644 --- a/src/fastapi_toolsets/crud/factory.py +++ b/src/fastapi_toolsets/crud/factory.py @@ -1211,12 +1211,26 @@ def CrudFactory( ) ``` """ + pk_key = model.__mapper__.primary_key[0].key + assert pk_key is not None + pk_col = getattr(model, pk_key) + + if searchable_fields is None: + effective_searchable = [pk_col] + else: + existing_keys = {f.key for f in searchable_fields if not isinstance(f, tuple)} + effective_searchable = ( + [pk_col, *searchable_fields] + if pk_key not in existing_keys + else list(searchable_fields) + ) + cls = type( f"Async{model.__name__}Crud", (AsyncCrud,), { "model": model, - "searchable_fields": searchable_fields, + "searchable_fields": effective_searchable, "facet_fields": facet_fields, "order_fields": order_fields, "m2m_fields": m2m_fields, diff --git a/tests/test_crud_search.py b/tests/test_crud_search.py index 7bf02a1..9421379 100644 --- a/tests/test_crud_search.py +++ b/tests/test_crud_search.py @@ -211,14 +211,17 @@ class TestPaginateSearch: assert result.data[0].username == "active_john" @pytest.mark.anyio - async def test_search_auto_detect_fields(self, db_session: AsyncSession): - """Auto-detect searchable fields when not specified.""" + async def test_search_explicit_fields(self, db_session: AsyncSession): + """Search works when search_fields are passed per call.""" await UserCrud.create( db_session, UserCreate(username="findme", email="other@test.com") ) result = await UserCrud.offset_paginate( - db_session, search="findme", schema=UserRead + db_session, + search="findme", + search_fields=[User.username], + schema=UserRead, ) assert isinstance(result.pagination, OffsetPagination)