Skip to content

Commit 27478ad

Browse files
authored
Allow TortoiseMixin.orm_get_list() to take additional parameters for related objects to prefetch, search, etc (#124)
* Allow TortoiseMixin.orm_get_list() to take additional parameters for related objects to prefetch, search, etc * Allow TortoiseMixin.orm_get_list() to take additional parameters for related objects to prefetch, search, etc
1 parent 99ab478 commit 27478ad

8 files changed

Lines changed: 283 additions & 11 deletions

File tree

docs/build.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -594,6 +594,34 @@ def get_page_context(page_url):
594594
"content": "Model-admin-specific methods and attributes:",
595595
},
596596
{"type": "code-python", "content": inspect.getsource(ModelAdmin)},
597+
{
598+
"type": "text",
599+
"content": "You can customize relation loading and relation search by overriding <code>orm_get_list</code> and forwarding <code>prefetch_related_fields</code> and <code>additional_search_fields</code>:",
600+
},
601+
{
602+
"type": "code-python",
603+
"content": """class TaskAdmin(TortoiseModelAdmin):
604+
search_fields = ("title",)
605+
606+
async def orm_get_list(
607+
self,
608+
offset=None,
609+
limit=None,
610+
search=None,
611+
sort_by=None,
612+
filters=None,
613+
):
614+
return await super().orm_get_list(
615+
offset=offset,
616+
limit=limit,
617+
search=search,
618+
sort_by=sort_by,
619+
filters=filters,
620+
prefetch_related_fields=["user"],
621+
additional_search_fields=["user__email"],
622+
)
623+
""",
624+
},
597625
]
598626
case "#model-form-field-types":
599627
return [

docs/index.html

Lines changed: 63 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ <h1>FastAdmin | Documentation</h1>
340340
</li>
341341
<li>
342342
<strong>Updated:</strong>
343-
22 February 2026
343+
23 February 2026
344344
</li>
345345
</ul>
346346
</div>
@@ -2269,6 +2269,8 @@ <h3>Methods and Attributes</h3>
22692269
search: str | None = None,
22702270
sort_by: str | None = None,
22712271
filters: dict | None = None,
2272+
prefetch_related_fields: list[str] | None = None,
2273+
additional_search_fields: list[str] | None = None,
22722274
) -> tuple[list[Any], int]:
22732275
"""This method is used to get list of orm/db model objects.
22742276

@@ -2277,6 +2279,8 @@ <h3>Methods and Attributes</h3>
22772279
:params search: a search query.
22782280
:params sort_by: a sort by field name.
22792281
:params filters: a dict of filters.
2282+
:params prefetch_related_fields: a list of related fields to prefetch.
2283+
:params additional_search_fields: a list of additional search fields.
22802284
:return: A tuple of list of objects and total count.
22812285
"""
22822286
raise NotImplementedError
@@ -2759,6 +2763,64 @@ <h3>Methods and Attributes</h3>
27592763

27602764

27612765

2766+
2767+
<p class="text-4">
2768+
You can customize relation loading and relation search by overriding <code>orm_get_list</code> and forwarding <code>prefetch_related_fields</code> and <code>additional_search_fields</code>:
2769+
</p>
2770+
2771+
2772+
2773+
2774+
2775+
2776+
2777+
2778+
2779+
2780+
2781+
2782+
2783+
2784+
2785+
2786+
2787+
2788+
2789+
2790+
2791+
2792+
2793+
2794+
2795+
<pre>
2796+
<code class="language-python">
2797+
class TaskAdmin(TortoiseModelAdmin):
2798+
search_fields = ("title",)
2799+
2800+
async def orm_get_list(
2801+
self,
2802+
offset=None,
2803+
limit=None,
2804+
search=None,
2805+
sort_by=None,
2806+
filters=None,
2807+
):
2808+
return await super().orm_get_list(
2809+
offset=offset,
2810+
limit=limit,
2811+
search=search,
2812+
sort_by=sort_by,
2813+
filters=filters,
2814+
prefetch_related_fields=["user"],
2815+
additional_search_fields=["user__email"],
2816+
)
2817+
2818+
</code>
2819+
</pre>
2820+
2821+
2822+
2823+
27622824
</section>
27632825

27642826

fastadmin/models/base.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,8 @@ async def orm_get_list(
227227
search: str | None = None,
228228
sort_by: str | None = None,
229229
filters: dict | None = None,
230+
prefetch_related_fields: list[str] | None = None,
231+
additional_search_fields: list[str] | None = None,
230232
) -> tuple[list[Any], int]:
231233
"""This method is used to get list of orm/db model objects.
232234
@@ -235,6 +237,8 @@ async def orm_get_list(
235237
:params search: a search query.
236238
:params sort_by: a sort by field name.
237239
:params filters: a dict of filters.
240+
:params prefetch_related_fields: a list of related fields to prefetch.
241+
:params additional_search_fields: a list of additional search fields.
238242
:return: A tuple of list of objects and total count.
239243
"""
240244
raise NotImplementedError

fastadmin/models/orms/django.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import operator
21
from base64 import b64decode
32
from typing import Any
43
from uuid import UUID
@@ -245,6 +244,8 @@ def orm_get_list(
245244
search: str | None = None,
246245
sort_by: str | None = None,
247246
filters: dict | None = None,
247+
prefetch_related_fields: list[str] | None = None,
248+
additional_search_fields: list[str] | None = None,
248249
) -> tuple[list[Any], int]:
249250
"""This method is used to get list of orm/db model objects.
250251
@@ -253,19 +254,30 @@ def orm_get_list(
253254
:params search: a search query.
254255
:params sort_by: a sort by field name.
255256
:params filters: a dict of filters.
257+
:params prefetch_related_fields: a list of related fields to prefetch.
258+
:params additional_search_fields: a list of additional search fields.
256259
:return: A tuple of list of objects and total count.
257260
"""
258261
qs = self.model_cls.objects.all()
259262

263+
if prefetch_related_fields:
264+
qs = qs.prefetch_related(*prefetch_related_fields)
265+
260266
if filters:
261267
for field_with_condition, value in filters.items():
262268
field = field_with_condition[0]
263269
condition = field_with_condition[1]
264270
qs = qs.filter(**{f"{field}__{condition}" if condition != "exact" else field: value})
265271

266-
if search and self.search_fields:
267-
search_conditions = [Q(**{f + "__icontains": search}) for f in self.search_fields]
268-
search_q = search_conditions[0] if len(search_conditions) == 1 else operator.or_(*search_conditions)
272+
search_fields = list(self.search_fields)
273+
if additional_search_fields:
274+
search_fields.extend(additional_search_fields)
275+
276+
if search and search_fields:
277+
search_conditions = [Q(**{f + "__icontains": search}) for f in search_fields]
278+
search_q = search_conditions[0]
279+
for condition in search_conditions[1:]:
280+
search_q |= condition
269281
qs = qs.filter(search_q)
270282

271283
if sort_by:

fastadmin/models/orms/ponyorm.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,8 @@ def orm_get_list(
224224
search: str | None = None,
225225
sort_by: str | None = None,
226226
filters: dict | None = None,
227+
prefetch_related_fields: list[str] | None = None,
228+
additional_search_fields: list[str] | None = None,
227229
) -> tuple[list[Any], int]:
228230
"""This method is used to get list of orm/db model objects.
229231
@@ -232,6 +234,8 @@ def orm_get_list(
232234
:params search: a search query.
233235
:params sort_by: a sort by field name.
234236
:params filters: a dict of filters.
237+
:params prefetch_related_fields: a list of related fields to prefetch.
238+
:params additional_search_fields: a list of additional search fields.
235239
:return: A tuple of list of objects and total count.
236240
"""
237241

@@ -271,9 +275,13 @@ def orm_get_list(
271275
filter_expr = f""""{value}" {pony_condition} m.{field}"""
272276
qs = qs.filter(filter_expr)
273277

274-
if search and self.search_fields:
278+
search_fields = list(self.search_fields)
279+
if additional_search_fields:
280+
search_fields.extend(additional_search_fields)
281+
282+
if search and search_fields:
275283
ids = []
276-
for search_field in self.search_fields:
284+
for search_field in search_fields:
277285
pony_search_field = search_field.replace("__", ".")
278286
# Pony string filter for case-insensitive search
279287
filter_expr = f'"{search.lower()}" in m.{pony_search_field}.lower()'
@@ -296,6 +304,9 @@ def orm_get_list(
296304
if self.list_select_related:
297305
qs = qs.prefetch(*[getattr(self.model_cls, field) for field in self.list_select_related])
298306

307+
if prefetch_related_fields:
308+
qs = qs.prefetch(*[getattr(self.model_cls, field) for field in prefetch_related_fields])
309+
299310
if offset is not None and limit is not None:
300311
qs = qs.limit(limit, offset=offset)
301312

fastadmin/models/orms/sqlalchemy.py

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,8 @@ async def orm_get_list(
300300
search: str | None = None,
301301
sort_by: str | None = None,
302302
filters: dict | None = None,
303+
prefetch_related_fields: list[str] | None = None,
304+
additional_search_fields: list[str] | None = None,
303305
) -> tuple[list[Any], int]:
304306
"""This method is used to get list of orm/db model objects.
305307
@@ -308,6 +310,8 @@ async def orm_get_list(
308310
:params search: a search query.
309311
:params sort_by: a sort by field name.
310312
:params filters: a dict of filters.
313+
:params prefetch_related_fields: a list of related fields to prefetch.
314+
:params additional_search_fields: a list of additional search fields.
311315
:return: A tuple of list of objects and total count.
312316
"""
313317

@@ -354,9 +358,13 @@ def convert_sort_by(sort_by: str) -> str:
354358
q.append(model_field.ilike(f"%{value}%"))
355359
qs = qs.where(and_(*q))
356360

357-
if search and self.search_fields:
361+
search_fields = list(self.search_fields)
362+
if additional_search_fields:
363+
search_fields.extend(additional_search_fields)
364+
365+
if search and search_fields:
358366
q = []
359-
for field in self.search_fields:
367+
for field in search_fields:
360368
condition = self._build_search_condition(field, search)
361369
if condition is not None:
362370
q.append(condition)
@@ -377,6 +385,28 @@ def convert_sort_by(sort_by: str) -> str:
377385
for field in self.list_select_related:
378386
qs = qs.options(selectinload(getattr(self.model_cls, field)))
379387

388+
if prefetch_related_fields:
389+
for field_path in prefetch_related_fields:
390+
parts = field_path.split("__")
391+
current_model = self.model_cls
392+
attr = getattr(current_model, parts[0], None)
393+
if attr is None:
394+
continue
395+
option = selectinload(attr)
396+
current_model = getattrs(attr, "property.mapper.class_")
397+
for part in parts[1:]:
398+
if current_model is None:
399+
break
400+
nested_attr = getattr(current_model, part, None)
401+
if nested_attr is None:
402+
break
403+
next_model = getattrs(nested_attr, "property.mapper.class_")
404+
if next_model is None:
405+
break
406+
option = option.selectinload(nested_attr)
407+
current_model = next_model
408+
qs = qs.options(option)
409+
380410
if offset is not None and limit is not None:
381411
qs = qs.offset(offset)
382412
qs = qs.limit(limit)

fastadmin/models/orms/tortoise.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,8 @@ async def orm_get_list(
254254
search: str | None = None,
255255
sort_by: str | None = None,
256256
filters: dict | None = None,
257+
prefetch_related_fields: list[str] | None = None,
258+
additional_search_fields: list[str] | None = None,
257259
) -> tuple[list[Any], int]:
258260
"""This method is used to get list of orm/db model objects.
259261
@@ -262,21 +264,30 @@ async def orm_get_list(
262264
:params search: a search query.
263265
:params sort_by: a sort by field name.
264266
:params filters: a dict of filters.
267+
:params prefetch_related_fields: a list of related fields to prefetch.
268+
:params additional_search_fields: a list of additional search fields.
265269
:return: A tuple of list of objects and total count.
266270
"""
267271
qs = self.model_cls.all()
268272

273+
if prefetch_related_fields:
274+
qs = qs.prefetch_related(*prefetch_related_fields).distinct()
275+
269276
if filters:
270277
for field_with_condition, value in filters.items():
271278
field = field_with_condition[0]
272279
condition = field_with_condition[1]
273280
qs = qs.filter(**{f"{field}__{condition}" if condition != "exact" else field: value})
274281

275-
if search and self.search_fields:
282+
search_fields = list(self.search_fields)
283+
if additional_search_fields:
284+
search_fields.extend(additional_search_fields)
285+
286+
if search and search_fields:
276287
qs = qs.filter(
277288
functools.reduce(
278289
operator.or_,
279-
(Q(**{f + "__icontains": search}) for f in self.search_fields),
290+
(Q(**{f + "__icontains": search}) for f in search_fields),
280291
Q(),
281292
)
282293
)

0 commit comments

Comments
 (0)