Skip to content

Contrib: MongoEngine

Full attribute and method reference for the MongoEngine backend (starlette_admin.contrib.mongoengine), generated from docstrings. For a task-oriented walkthrough, see MongoEngine.

starlette_admin.contrib.mongoengine.admin.Admin

Bases: BaseAdmin

MongoEngine-flavored BaseAdmin that also mounts the GridFS file-serving route.

Source code in starlette_admin/contrib/mongoengine/admin.py
class Admin(BaseAdmin):
    """MongoEngine-flavored `BaseAdmin` that also mounts the GridFS file-serving route."""

    def mount_to(self, app: Starlette) -> None:
        self.routes.append(
            Route(
                "/api/file/{db}/{col}/{pk}",
                _serve_file,
                methods=["GET"],
                name="api:file",
            )
        )
        super().mount_to(app)

starlette_admin.contrib.mongoengine.view.ModelView

Bases: BaseModelView

BaseModelView backed by a mongoengine.Document.

Wraps a document class and implements CRUD, search, filtering, and sorting against it, converting its declared fields to admin fields via converter.

Source code in starlette_admin/contrib/mongoengine/view.py
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
class ModelView(BaseModelView):
    """`BaseModelView` backed by a `mongoengine.Document`.

    Wraps a document class and implements CRUD, search, filtering, and sorting
    against it, converting its declared fields to admin fields via `converter`.
    """

    def __init__(
        self,
        document: type[me.Document],
        icon: str | None = None,
        display_name: str | None = None,
        menu_label: str | None = None,
        key: str | None = None,
        converter: BaseMongoEngineModelConverter | None = None,
    ):
        """
        Parameters:
            document: The mongoengine document class this view manages.
            icon: CSS class for the icon shown in the admin menu.
            display_name: Display name for the view. Defaults to the prettified
                document class name.
            menu_label: Display label for the view. Defaults to the pluralized,
                prettified document class name.
            key: URL-safe identifier for the view. Defaults to the
                slugified document class name.
            converter: Converter used to turn the document's fields into admin
                fields. Defaults to `ModelConverter()`.
        """
        self.document = document
        self.key = key or self.key or slugify_class_name(self.document.__name__)
        self.menu_label = (
            menu_label
            or self.menu_label
            or prettify_class_name(self.document.__name__) + "s"
        )
        self.display_name = (
            display_name
            or self.display_name
            or prettify_class_name(self.document.__name__)
        )
        self.icon = icon or self.icon
        self.pk_attr = "id"
        if self.fields is None or len(self.fields) == 0:
            self.fields = document._fields_ordered  # ty: ignore[unresolved-attribute]
        self.fields = (converter or ModelConverter()).convert_fields_list(
            fields=self.fields, model=self.document
        )
        self.exclude_fields_from_list = (
            normalize_list(self.exclude_fields_from_list) or []
        )
        self.exclude_fields_from_detail = (
            normalize_list(self.exclude_fields_from_detail) or []
        )
        self.exclude_fields_from_create = (
            normalize_list(self.exclude_fields_from_create) or []
        )
        self.exclude_fields_from_edit = (
            normalize_list(self.exclude_fields_from_edit) or []
        )
        self.exclude_fields_from_export = (
            normalize_list(self.exclude_fields_from_export) or []
        )
        self.exclude_fields_from_import = (
            normalize_list(self.exclude_fields_from_import) or []
        )
        self.searchable_fields = normalize_list(self.searchable_fields)
        self.sortable_fields = normalize_list(self.sortable_fields)
        self.fields_default_sort = normalize_list(
            self.fields_default_sort, is_default_sort_list=True
        )
        super().__init__()

    def get_filter_registry(self) -> FilterRegistry:
        """Return the registry used to resolve available filters for this view's fields."""
        return MongoEngineFilterRegistry()

    async def count(
        self,
        request: Request,
        q: str | None = None,
        filters: FilterGroup | None = None,
    ) -> int:
        """Return the number of documents matching the search term and filters."""
        qs = await self._build_query(request, q, filters)
        total = self.document.objects(qs).count()  # ty: ignore[unresolved-attribute]
        _log.debug("count: key=%r q=%r total=%d", self.key, q, total)
        return total

    async def find_all(
        self,
        request: Request,
        skip: int = 0,
        limit: int = 100,
        q: str | None = None,
        sorts: Sequence[tuple[str, str]] | None = None,
        filters: FilterGroup | None = None,
    ) -> Sequence[Any]:
        """Return a page of documents matching the search term and filters, sorted
        according to `sorts`.

        A non-positive `limit` returns every matching document from `skip` onward.
        """
        _log.debug(
            "find_all: key=%r skip=%d limit=%d q=%r",
            self.key,
            skip,
            limit,
            q,
        )
        qs = await self._build_query(request, q, filters)
        objs = self.document.objects(qs).order_by(  # ty: ignore[unresolved-attribute]
            *build_order_clauses(sorts or [])
        )
        if limit > 0:
            return objs[skip : skip + limit]
        return objs[skip:]

    async def find_by_pk(self, request: Request, pk: Any) -> me.Document | None:
        """Return the document with primary key `pk`, or `None` if it does not
        exist or `pk` is not a valid ObjectId.
        """
        try:
            obj = self.document.objects(id=pk).get()  # ty: ignore[unresolved-attribute]
            _log.debug("find_by_pk: key=%r pk=%s found", self.key, pk)
            return obj
        except (DoesNotExist, ValidationError):
            _log.debug("find_by_pk: key=%r pk=%s not found", self.key, pk)
            return None

    async def find_by_pks(
        self, request: Request, pks: list[Any]
    ) -> Sequence[me.Document]:
        """Return the documents whose primary key is in `pks`."""
        return self.document.objects(id__in=pks)  # ty: ignore[unresolved-attribute]

    async def get_serialized_pk_value(self, request: Request, obj: Any) -> Any:
        return str(await self.get_pk_value(request, obj))

    async def create(self, request: Request, data: dict[str, Any]) -> Any:
        """Create and save a new document from converted form data.

        Emits `BeforeCreateContext`/`AfterCreateContext` events around the save.
        Exceptions are routed through `handle_exception`.
        """
        _log.debug("create: key=%r populating new document", self.key)
        try:
            await self.validate(request, data)
            obj = await self._populate_obj(request, self.document(), data)
            await self._emit_before_create(request, data, obj)
            obj.save()
            _log.info("create: key=%r pk=%s created", self.key, obj.pk)
            await self._emit_after_create(request, obj)
            return obj
        except Exception as e:
            await self.handle_exception(request, e)

    async def edit(self, request: Request, pk: Any, data: dict[str, Any]) -> Any:
        """Apply converted form data to the document identified by `pk` and save it.

        Emits `BeforeEditContext`/`AfterEditContext` events around the save.
        Exceptions are routed through `handle_exception`.
        """
        _log.debug("edit: key=%r pk=%s populating document", self.key, pk)
        try:
            await self.validate(request, data)
            obj = await self.find_by_pk(request, pk)
            obj = await self._populate_obj(
                request,
                obj,  # ty: ignore[invalid-argument-type]
                data,
                True,
            )
            await self._emit_before_edit(request, data, obj, pk=pk)
            obj.save()
            _log.info("edit: key=%r pk=%s saved", self.key, pk)
            await self._emit_after_edit(request, obj, pk=pk)
            return obj
        except Exception as e:
            await self.handle_exception(request, e)

    async def _populate_obj(
        self,
        request: Request,
        obj: me.Document,
        data: dict[str, Any],
        is_edit: bool = False,
        document: type[BaseDocument] | None = None,
        fields: Sequence[sa.BaseField] | None = None,
    ) -> me.Document:
        """Assign converted form values onto `obj`, one field at a time.

        Skips read-only fields. `document` and `fields` let this be reused for
        embedded documents (see `_handle_embedded_field`), where `obj` is an
        `EmbeddedDocument` instance rather than the top-level document.

        Parameters:
            request: The request being processed.
            obj: The document (or embedded document) instance to populate.
            data: The converted form data, keyed by field name.
            is_edit: `True` when populating for an edit rather than a create.
            document: The document class `fields` is resolved against. Defaults
                to `self.document`.
            fields: The admin fields to populate. Defaults to the view's field list.

        Returns:
            `obj`, populated in place.
        """
        if document is None:
            document = self.document
        if fields is None:
            fields = self.get_fields_list(request)
        for field in fields:
            if field.read_only:
                continue
            name, value = field.name, data.get(field.name)
            me_field = getattr(document, name)
            await self._set_field(request, obj, name, value, me_field, field, is_edit)
        return obj

    async def _set_field(
        self,
        request: Request,
        obj: me.Document,
        name: str,
        value: Any,
        me_field: Any,
        field: sa.BaseField,
        is_edit: bool,
    ) -> None:
        """Assign `value` to `obj`'s `name` attribute, dispatching on field type:
        file fields go through GridFS handling, embedded documents and lists of
        embedded documents recurse through `_populate_obj`, relation fields are
        converted to `ObjectId`, and everything else is a plain `setattr`.
        """
        if isinstance(field, (FileField, ImageField)):
            self._handle_file_field(obj, name, value)
        elif isinstance(me_field, me.EmbeddedDocumentField) and value is not None:
            await self._handle_embedded_field(
                request, obj, name, value, me_field, field, is_edit
            )
        elif (
            isinstance(me_field, me.ListField)
            and isinstance(me_field.field, me.EmbeddedDocumentField)
            and value is not None
        ):
            await self._handle_embedded_list_field(
                request, obj, name, value, me_field, field, is_edit
            )
        elif isinstance(field, sa.HasOne) and value is not None:
            setattr(obj, name, ObjectId(value))
        elif isinstance(field, sa.HasMany) and value is not None:
            setattr(obj, name, [ObjectId(v) for v in value])
        else:
            setattr(obj, name, value)

    def _handle_file_field(self, obj: me.Document, name: str, value: Any) -> None:
        """Delete, replace, or store a file in the GridFS proxy at `obj.<name>`.

        `value` is the `(upload, should_be_deleted)` tuple that `FileField.parse_form_data`
        produces: `should_be_deleted` clears the current file, an `UploadFile`
        replaces or stores it, and any other value leaves the field untouched.
        """
        proxy: GridFSProxy = getattr(obj, name)
        value, should_be_deleted = not_none(value)
        if should_be_deleted:
            proxy.delete()
        elif isinstance(value, UploadFile):
            if proxy.grid_id is not None:
                proxy.replace(
                    value.file, filename=value.filename, content_type=value.content_type
                )
            else:
                proxy.put(
                    value.file, filename=value.filename, content_type=value.content_type
                )

    async def _handle_embedded_field(
        self,
        request: Request,
        obj: me.Document,
        name: str,
        value: Any,
        me_field: me.EmbeddedDocumentField,
        field: sa.BaseField,
        is_edit: bool,
    ) -> None:
        """Populate `obj.<name>`, an `EmbeddedDocumentField`, from `value`.

        Reuses the existing embedded document when present so unrelated
        attributes are preserved; otherwise instantiates a new one via
        `me_field.document_type`.
        """
        assert isinstance(field, sa.CollectionField)
        old_value = getattr(obj, name, None)
        if old_value is None:
            old_value = me_field.document_type()
        setattr(
            obj,
            name,
            await self._populate_obj(
                request, old_value, value, is_edit, me_field.document_type, field.fields
            ),
        )

    async def _handle_embedded_list_field(
        self,
        request: Request,
        obj: me.Document,
        name: str,
        value: list[Any],
        me_field: me.ListField,
        field: sa.BaseField,
        is_edit: bool,
    ) -> None:
        """Populate `obj.<name>`, a list of embedded documents, from `value`.

        Reuses as many existing embedded documents as possible, in order, and
        appends freshly instantiated ones (via `me_field.field.document_type`)
        if `value` is longer than the current list. Extra existing entries
        beyond `len(value)` are dropped by the final `setattr`.
        """
        assert isinstance(field, sa.ListField) and isinstance(
            field.field, sa.CollectionField
        )
        assert me_field.field is not None
        old_value = getattr(obj, name, [])
        if len(old_value) < len(value):
            old_value.extend(
                [
                    me_field.field.document_type()
                    for _ in range(len(value) - len(old_value))
                ]
            )
        setattr(
            obj,
            name,
            [
                await self._populate_obj(
                    request,
                    old_value[idx],
                    _val,
                    is_edit,
                    me_field.field.document_type,
                    field.field.fields,
                )
                for idx, _val in enumerate(value)
            ],
        )

    async def delete(self, request: Request, pks: list[Any]) -> int | None:
        """Delete the documents whose primary key is in `pks`.

        Emits `BeforeDeleteContext`/`AfterDeleteContext` events for each
        affected document around the bulk delete.

        Returns:
            The number of documents deleted.
        """
        _log.debug("delete: key=%r pks=%s", self.key, pks)
        objs = self.document.objects(id__in=pks)  # ty: ignore[unresolved-attribute]
        for obj in objs:
            await self._emit_before_delete(
                request, await self.get_pk_value(request, obj), obj
            )
        deleted_count = objs.delete()
        _log.info("delete: key=%r pks=%s affected=%s", self.key, pks, deleted_count)
        for obj in objs:
            await self._emit_after_delete(
                request, await self.get_pk_value(request, obj), obj
            )
        return deleted_count

    async def handle_exception(self, request: Request, exc: Exception) -> None:
        """Translate a mongoengine `ValidationError` into a `FormValidationError`
        so field-level errors surface on the form. Any other exception is logged
        and re-raised unchanged.
        """
        if isinstance(exc, FormValidationError):
            raise exc
        if isinstance(exc, ValidationError):
            _log.debug(
                "handle_exception: key=%r validation error: %s",
                self.key,
                exc.to_dict(),
            )
            raise FormValidationError(exc.to_dict())
        _log.error(
            "handle_exception: key=%r unexpected error: %s",
            self.key,
            exc,
            exc_info=exc,
        )
        raise exc  # pragma: no cover

    async def _build_query(
        self,
        request: Request,
        q: str | None = None,
        filters: FilterGroup | None = None,
    ) -> QNode:
        """Combine the full-text search term and the parsed filter tree into a
        single `QNode`, ANDing the two together when both are present.
        """
        qs = Q.empty()
        if q is not None:
            qs = await self.build_full_text_search_query(request, q)
        if filters is not None and not filters.is_empty():
            fields_by_name = {f.name: f for f in self.get_fields_list(request)}
            registry = self.get_filter_registry()
            filter_q = build_filter_query(
                filters, fields_by_name, registry, self, request
            )
            if filter_q is not None:
                qs = qs & filter_q
        return qs

    async def build_full_text_search_query(self, request: Request, term: str) -> QNode:
        """Build a `QNode` matching `term` case-insensitively against every
        searchable text-like field (string, text area, email, URL, phone, color),
        excluding the primary key. Fragments are combined with `|` (OR).
        """
        queries = []
        for field in self.get_fields_list(request):
            if (
                field.searchable
                and field.name != "id"
                and type(field)
                in [
                    sa.StringField,
                    sa.TextAreaField,
                    sa.EmailField,
                    sa.URLField,
                    sa.PhoneField,
                    sa.ColorField,
                ]
            ):
                queries.append(Q(field.name, term, "icontains"))
        return (
            functools.reduce(lambda q1, q2: q1 | q2, queries) if queries else Q.empty()
        )

__init__(document, icon=None, display_name=None, menu_label=None, key=None, converter=None)

Parameters:

Name Type Description Default
document type[Document]

The mongoengine document class this view manages.

required
icon str | None

CSS class for the icon shown in the admin menu.

None
display_name str | None

Display name for the view. Defaults to the prettified document class name.

None
menu_label str | None

Display label for the view. Defaults to the pluralized, prettified document class name.

None
key str | None

URL-safe identifier for the view. Defaults to the slugified document class name.

None
converter BaseMongoEngineModelConverter | None

Converter used to turn the document's fields into admin fields. Defaults to ModelConverter().

None
Source code in starlette_admin/contrib/mongoengine/view.py
def __init__(
    self,
    document: type[me.Document],
    icon: str | None = None,
    display_name: str | None = None,
    menu_label: str | None = None,
    key: str | None = None,
    converter: BaseMongoEngineModelConverter | None = None,
):
    """
    Parameters:
        document: The mongoengine document class this view manages.
        icon: CSS class for the icon shown in the admin menu.
        display_name: Display name for the view. Defaults to the prettified
            document class name.
        menu_label: Display label for the view. Defaults to the pluralized,
            prettified document class name.
        key: URL-safe identifier for the view. Defaults to the
            slugified document class name.
        converter: Converter used to turn the document's fields into admin
            fields. Defaults to `ModelConverter()`.
    """
    self.document = document
    self.key = key or self.key or slugify_class_name(self.document.__name__)
    self.menu_label = (
        menu_label
        or self.menu_label
        or prettify_class_name(self.document.__name__) + "s"
    )
    self.display_name = (
        display_name
        or self.display_name
        or prettify_class_name(self.document.__name__)
    )
    self.icon = icon or self.icon
    self.pk_attr = "id"
    if self.fields is None or len(self.fields) == 0:
        self.fields = document._fields_ordered  # ty: ignore[unresolved-attribute]
    self.fields = (converter or ModelConverter()).convert_fields_list(
        fields=self.fields, model=self.document
    )
    self.exclude_fields_from_list = (
        normalize_list(self.exclude_fields_from_list) or []
    )
    self.exclude_fields_from_detail = (
        normalize_list(self.exclude_fields_from_detail) or []
    )
    self.exclude_fields_from_create = (
        normalize_list(self.exclude_fields_from_create) or []
    )
    self.exclude_fields_from_edit = (
        normalize_list(self.exclude_fields_from_edit) or []
    )
    self.exclude_fields_from_export = (
        normalize_list(self.exclude_fields_from_export) or []
    )
    self.exclude_fields_from_import = (
        normalize_list(self.exclude_fields_from_import) or []
    )
    self.searchable_fields = normalize_list(self.searchable_fields)
    self.sortable_fields = normalize_list(self.sortable_fields)
    self.fields_default_sort = normalize_list(
        self.fields_default_sort, is_default_sort_list=True
    )
    super().__init__()

build_full_text_search_query(request, term) async

Build a QNode matching term case-insensitively against every searchable text-like field (string, text area, email, URL, phone, color), excluding the primary key. Fragments are combined with | (OR).

Source code in starlette_admin/contrib/mongoengine/view.py
async def build_full_text_search_query(self, request: Request, term: str) -> QNode:
    """Build a `QNode` matching `term` case-insensitively against every
    searchable text-like field (string, text area, email, URL, phone, color),
    excluding the primary key. Fragments are combined with `|` (OR).
    """
    queries = []
    for field in self.get_fields_list(request):
        if (
            field.searchable
            and field.name != "id"
            and type(field)
            in [
                sa.StringField,
                sa.TextAreaField,
                sa.EmailField,
                sa.URLField,
                sa.PhoneField,
                sa.ColorField,
            ]
        ):
            queries.append(Q(field.name, term, "icontains"))
    return (
        functools.reduce(lambda q1, q2: q1 | q2, queries) if queries else Q.empty()
    )

count(request, q=None, filters=None) async

Return the number of documents matching the search term and filters.

Source code in starlette_admin/contrib/mongoengine/view.py
async def count(
    self,
    request: Request,
    q: str | None = None,
    filters: FilterGroup | None = None,
) -> int:
    """Return the number of documents matching the search term and filters."""
    qs = await self._build_query(request, q, filters)
    total = self.document.objects(qs).count()  # ty: ignore[unresolved-attribute]
    _log.debug("count: key=%r q=%r total=%d", self.key, q, total)
    return total

create(request, data) async

Create and save a new document from converted form data.

Emits BeforeCreateContext/AfterCreateContext events around the save. Exceptions are routed through handle_exception.

Source code in starlette_admin/contrib/mongoengine/view.py
async def create(self, request: Request, data: dict[str, Any]) -> Any:
    """Create and save a new document from converted form data.

    Emits `BeforeCreateContext`/`AfterCreateContext` events around the save.
    Exceptions are routed through `handle_exception`.
    """
    _log.debug("create: key=%r populating new document", self.key)
    try:
        await self.validate(request, data)
        obj = await self._populate_obj(request, self.document(), data)
        await self._emit_before_create(request, data, obj)
        obj.save()
        _log.info("create: key=%r pk=%s created", self.key, obj.pk)
        await self._emit_after_create(request, obj)
        return obj
    except Exception as e:
        await self.handle_exception(request, e)

delete(request, pks) async

Delete the documents whose primary key is in pks.

Emits BeforeDeleteContext/AfterDeleteContext events for each affected document around the bulk delete.

Returns:

Type Description
int | None

The number of documents deleted.

Source code in starlette_admin/contrib/mongoengine/view.py
async def delete(self, request: Request, pks: list[Any]) -> int | None:
    """Delete the documents whose primary key is in `pks`.

    Emits `BeforeDeleteContext`/`AfterDeleteContext` events for each
    affected document around the bulk delete.

    Returns:
        The number of documents deleted.
    """
    _log.debug("delete: key=%r pks=%s", self.key, pks)
    objs = self.document.objects(id__in=pks)  # ty: ignore[unresolved-attribute]
    for obj in objs:
        await self._emit_before_delete(
            request, await self.get_pk_value(request, obj), obj
        )
    deleted_count = objs.delete()
    _log.info("delete: key=%r pks=%s affected=%s", self.key, pks, deleted_count)
    for obj in objs:
        await self._emit_after_delete(
            request, await self.get_pk_value(request, obj), obj
        )
    return deleted_count

edit(request, pk, data) async

Apply converted form data to the document identified by pk and save it.

Emits BeforeEditContext/AfterEditContext events around the save. Exceptions are routed through handle_exception.

Source code in starlette_admin/contrib/mongoengine/view.py
async def edit(self, request: Request, pk: Any, data: dict[str, Any]) -> Any:
    """Apply converted form data to the document identified by `pk` and save it.

    Emits `BeforeEditContext`/`AfterEditContext` events around the save.
    Exceptions are routed through `handle_exception`.
    """
    _log.debug("edit: key=%r pk=%s populating document", self.key, pk)
    try:
        await self.validate(request, data)
        obj = await self.find_by_pk(request, pk)
        obj = await self._populate_obj(
            request,
            obj,  # ty: ignore[invalid-argument-type]
            data,
            True,
        )
        await self._emit_before_edit(request, data, obj, pk=pk)
        obj.save()
        _log.info("edit: key=%r pk=%s saved", self.key, pk)
        await self._emit_after_edit(request, obj, pk=pk)
        return obj
    except Exception as e:
        await self.handle_exception(request, e)

find_all(request, skip=0, limit=100, q=None, sorts=None, filters=None) async

Return a page of documents matching the search term and filters, sorted according to sorts.

A non-positive limit returns every matching document from skip onward.

Source code in starlette_admin/contrib/mongoengine/view.py
async def find_all(
    self,
    request: Request,
    skip: int = 0,
    limit: int = 100,
    q: str | None = None,
    sorts: Sequence[tuple[str, str]] | None = None,
    filters: FilterGroup | None = None,
) -> Sequence[Any]:
    """Return a page of documents matching the search term and filters, sorted
    according to `sorts`.

    A non-positive `limit` returns every matching document from `skip` onward.
    """
    _log.debug(
        "find_all: key=%r skip=%d limit=%d q=%r",
        self.key,
        skip,
        limit,
        q,
    )
    qs = await self._build_query(request, q, filters)
    objs = self.document.objects(qs).order_by(  # ty: ignore[unresolved-attribute]
        *build_order_clauses(sorts or [])
    )
    if limit > 0:
        return objs[skip : skip + limit]
    return objs[skip:]

find_by_pk(request, pk) async

Return the document with primary key pk, or None if it does not exist or pk is not a valid ObjectId.

Source code in starlette_admin/contrib/mongoengine/view.py
async def find_by_pk(self, request: Request, pk: Any) -> me.Document | None:
    """Return the document with primary key `pk`, or `None` if it does not
    exist or `pk` is not a valid ObjectId.
    """
    try:
        obj = self.document.objects(id=pk).get()  # ty: ignore[unresolved-attribute]
        _log.debug("find_by_pk: key=%r pk=%s found", self.key, pk)
        return obj
    except (DoesNotExist, ValidationError):
        _log.debug("find_by_pk: key=%r pk=%s not found", self.key, pk)
        return None

find_by_pks(request, pks) async

Return the documents whose primary key is in pks.

Source code in starlette_admin/contrib/mongoengine/view.py
async def find_by_pks(
    self, request: Request, pks: list[Any]
) -> Sequence[me.Document]:
    """Return the documents whose primary key is in `pks`."""
    return self.document.objects(id__in=pks)  # ty: ignore[unresolved-attribute]

get_filter_registry()

Return the registry used to resolve available filters for this view's fields.

Source code in starlette_admin/contrib/mongoengine/view.py
def get_filter_registry(self) -> FilterRegistry:
    """Return the registry used to resolve available filters for this view's fields."""
    return MongoEngineFilterRegistry()

handle_exception(request, exc) async

Translate a mongoengine ValidationError into a FormValidationError so field-level errors surface on the form. Any other exception is logged and re-raised unchanged.

Source code in starlette_admin/contrib/mongoengine/view.py
async def handle_exception(self, request: Request, exc: Exception) -> None:
    """Translate a mongoengine `ValidationError` into a `FormValidationError`
    so field-level errors surface on the form. Any other exception is logged
    and re-raised unchanged.
    """
    if isinstance(exc, FormValidationError):
        raise exc
    if isinstance(exc, ValidationError):
        _log.debug(
            "handle_exception: key=%r validation error: %s",
            self.key,
            exc.to_dict(),
        )
        raise FormValidationError(exc.to_dict())
    _log.error(
        "handle_exception: key=%r unexpected error: %s",
        self.key,
        exc,
        exc_info=exc,
    )
    raise exc  # pragma: no cover

starlette_admin.contrib.mongoengine.view.InlineModelView

Bases: InlineModelView, ModelView

Inline editing of MongoEngine-backed related documents inside a parent form.

Declare document as a class attribute. The parent reference field is auto-detected by scanning the child document's fields for a ReferenceField whose document_type matches the parent document.

Example::

class CommentInline(InlineModelView):
    document = Comment
    fields = ["id", "author", "body"]
    extra = 2

class ArticleView(ModelView):
    document = Article
    inlines = [CommentInline]
Source code in starlette_admin/contrib/mongoengine/view.py
class InlineModelView(BaseInlineModelView, ModelView):
    """Inline editing of MongoEngine-backed related documents inside a parent form.

    Declare ``document`` as a class attribute. The parent reference field is
    auto-detected by scanning the child document's fields for a
    ``ReferenceField`` whose ``document_type`` matches the parent document.

    Example::

        class CommentInline(InlineModelView):
            document = Comment
            fields = ["id", "author", "body"]
            extra = 2

        class ArticleView(ModelView):
            document = Article
            inlines = [CommentInline]
    """

    document: ClassVar[type[me.Document]]

    def __init__(self, parent_view: BaseModelView | None = None) -> None:
        self.parent_view = parent_view
        ModelView.__init__(self, type(self).document)
        if not self.fk_attr:
            self.fk_attr = self._detect_fk_from_reference_field()

    def _detect_fk_from_reference_field(self) -> str:
        """Scan the child document's fields for a single `ReferenceField` pointing
        back at the parent document, and return its name for use as `fk_attr`.

        Raises:
            ValueError: If the child document has zero or more than one
                matching `ReferenceField`, since `fk_attr` cannot be inferred
                unambiguously in that case.
        """
        assert self.parent_view is not None, (
            "parent_view must be set to auto-detect fk_attr"
        )
        ref_fields = [
            name
            for name, field in self.document._fields.items()  # ty: ignore[unresolved-attribute]
            if isinstance(field, me.ReferenceField)
            and field.document_type is self.parent_view.document  # ty: ignore[unresolved-attribute]
        ]
        if len(ref_fields) == 1:
            _log.debug(
                "%s: auto-detected fk_attr=%r (single ReferenceField in document)",
                type(self).__name__,
                ref_fields[0],
            )
            return ref_fields[0]
        if len(ref_fields) == 0:
            raise ValueError(
                f"{type(self).__name__}: cannot auto-detect fk_attr. "
                f"{self.document.__name__} has no ReferenceField. "
                "Set fk_attr explicitly."
            )
        raise ValueError(
            f"{type(self).__name__}: cannot auto-detect fk_attr. "
            f"{self.document.__name__} has multiple ReferenceFields "
            f"({ref_fields}). Set fk_attr explicitly."
        )

    async def find_by_parent(self, request: Request, parent: Any) -> Sequence[Any]:
        """Return the child documents whose `fk_attr` equals `parent`'s primary key."""
        # Composite fk_attr (see BaseInlineModelView) is not supported here.
        assert isinstance(self.fk_attr, str)
        _log.debug(
            "find_by_parent %s: parent pk=%s fk_attr=%r",
            self.document.__name__,
            parent.pk,
            self.fk_attr,
        )
        rows = list(
            self.document.objects(**{self.fk_attr: parent.pk})  # ty: ignore[unresolved-attribute]
        )
        _log.debug("find_by_parent %s → %d row(s)", self.document.__name__, len(rows))
        return rows

    async def _populate_obj(
        self,
        request: Request,
        obj: me.Document,
        data: dict[str, Any],
        is_edit: bool = False,
        document: type[BaseDocument] | None = None,
        fields: Sequence[sa.BaseField] | None = None,
    ) -> me.Document:
        """Populate `obj` via the base `ModelView` logic, then stamp the parent
        foreign key onto newly created children.

        The `fk_attr` field is typically excluded from the inline form (its
        value comes from the parent context), so it is set explicitly here
        from `data` rather than through the field-by-field loop in the base
        implementation. This only applies on create: an existing child's
        parent reference is not reassigned on edit.
        """
        await super()._populate_obj(request, obj, data, is_edit, document, fields)
        # Composite fk_attr (see BaseInlineModelView) is not supported here.
        assert isinstance(self.fk_attr, str)
        if not is_edit and self.fk_attr in data:
            _log.debug(
                "_populate_objtry obj : %s, fk : %s, %s", obj, self.fk_attr, data
            )
            setattr(obj, self.fk_attr, data[self.fk_attr])
        return obj

find_by_parent(request, parent) async

Return the child documents whose fk_attr equals parent's primary key.

Source code in starlette_admin/contrib/mongoengine/view.py
async def find_by_parent(self, request: Request, parent: Any) -> Sequence[Any]:
    """Return the child documents whose `fk_attr` equals `parent`'s primary key."""
    # Composite fk_attr (see BaseInlineModelView) is not supported here.
    assert isinstance(self.fk_attr, str)
    _log.debug(
        "find_by_parent %s: parent pk=%s fk_attr=%r",
        self.document.__name__,
        parent.pk,
        self.fk_attr,
    )
    rows = list(
        self.document.objects(**{self.fk_attr: parent.pk})  # ty: ignore[unresolved-attribute]
    )
    _log.debug("find_by_parent %s → %d row(s)", self.document.__name__, len(rows))
    return rows

Fields

starlette_admin.contrib.mongoengine.fields.ObjectIdField dataclass

Bases: StringField

A string field whose underlying value is a MongoDB ObjectId.

Registered separately from StringField so the filter registry can bind ObjectId-aware filters (eq/neq/in/not_in only) instead of string filters.

Source code in starlette_admin/contrib/mongoengine/fields.py
@dataclass
class ObjectIdField(BaseStringField):
    """A string field whose underlying value is a MongoDB ObjectId.

    Registered separately from StringField so the filter registry can bind
    ObjectId-aware filters (eq/neq/in/not_in only) instead of string filters.
    """

    copy_to_clipboard: bool | None = True

starlette_admin.contrib.mongoengine.fields.FileField dataclass

Bases: FileField

FileField backed by a MongoEngine GridFSProxy.

Serializes the stored file to a {filename, content_type, url} dict instead of the base class's default handling.

Source code in starlette_admin/contrib/mongoengine/fields.py
@dataclass
class FileField(BaseFileField):
    """FileField backed by a MongoEngine `GridFSProxy`.

    Serializes the stored file to a `{filename, content_type, url}` dict
    instead of the base class's default handling.
    """

    async def serialize_value(self, request: Request, value: Any) -> Any:
        return _serialize_file_field(request, value)

starlette_admin.contrib.mongoengine.fields.ImageField dataclass

Bases: ImageField

ImageField backed by a MongoEngine GridFSProxy.

Serializes the same way as FileField, and serves the proxy's thumbnail_id (when present) in the list view.

Source code in starlette_admin/contrib/mongoengine/fields.py
@dataclass
class ImageField(BaseImageField):
    """ImageField backed by a MongoEngine `GridFSProxy`.

    Serializes the same way as [FileField][starlette_admin.contrib.mongoengine.fields.FileField],
    and serves the proxy's `thumbnail_id` (when present) in the list view.
    """

    async def serialize_value(self, request: Request, value: Any) -> Any:
        return _serialize_file_field(request, value)

Converters

starlette_admin.contrib.mongoengine.converters.BaseMongoEngineModelConverter

Bases: BaseModelConverter

Base class for converting mongoengine document fields to admin fields.

Subclasses register one converter method per mongoengine field type using the [converts][starlette_admin.converters.converts] decorator.

Source code in starlette_admin/contrib/mongoengine/converters.py
class BaseMongoEngineModelConverter(BaseModelConverter):
    """Base class for converting mongoengine document fields to admin fields.

    Subclasses register one converter method per mongoengine field type using
    the [converts][starlette_admin.converters.converts] decorator.
    """

    def _external_converters(self) -> dict[Any, Callable[..., sa.BaseField]]:
        return _EXTERNAL_CONVERTERS

    def get_converter(self, field: me.BaseField) -> Callable[..., sa.BaseField]:
        """Look up the converter function registered for `field`'s type.

        Tries an exact class match first, then falls back to the first
        registered class that `field` is an instance of, so a converter
        registered for a base mongoengine field type also matches its subclasses.

        Raises:
            NotSupportedField: If no registered converter matches `field`'s type.
        """
        if field.__class__ in self.converters:
            return self.converters[field.__class__]
        for cls, converter in self.converters.items():
            if isinstance(field, cls):
                return converter
        raise NotSupportedField(
            f"Field {field.__class__.__name__} can not be converted automatically. Find the appropriate field "
            "manually or provide your custom converter"
        )

    def convert(self, *args: Any, **kwargs: Any) -> sa.BaseField:
        """Convert a single mongoengine field, passed as the `field` keyword argument,
        to its admin field equivalent.
        """
        return self.get_converter(cast(me.BaseField, kwargs.get("field")))(
            *args, **kwargs
        )

    def convert_fields_list(
        self,
        *,
        fields: Sequence[Any],
        model: type[me.Document],
        **kwargs: Any,
    ) -> Sequence[sa.BaseField]:
        """Convert a mixed list of field specs into a list of admin fields.

        Each entry in `fields` may already be a `sa.BaseField` instance (passed
        through unchanged), a mongoengine field instance, or a string naming an
        attribute on `model`.

        Parameters:
            fields: The field specs to convert.
            model: The document class `fields` entries are resolved against
                when given as attribute-name strings.

        Returns:
            The converted admin fields, in the same order as `fields`.

        Raises:
            ValueError: If a string entry does not name an attribute on `model`.
        """
        converted_fields = []
        for value in fields:
            if isinstance(value, sa.BaseField):
                converted_fields.append(value)
            else:
                if isinstance(value, me.BaseField):
                    field = value
                elif isinstance(value, str) and hasattr(model, value):
                    field = getattr(model, value)
                else:
                    raise ValueError(f"Can't find field with key {value}")
                converted_fields.append(self.convert(field=field))
        return converted_fields

convert(*args, **kwargs)

Convert a single mongoengine field, passed as the field keyword argument, to its admin field equivalent.

Source code in starlette_admin/contrib/mongoengine/converters.py
def convert(self, *args: Any, **kwargs: Any) -> sa.BaseField:
    """Convert a single mongoengine field, passed as the `field` keyword argument,
    to its admin field equivalent.
    """
    return self.get_converter(cast(me.BaseField, kwargs.get("field")))(
        *args, **kwargs
    )

convert_fields_list(*, fields, model, **kwargs)

Convert a mixed list of field specs into a list of admin fields.

Each entry in fields may already be a sa.BaseField instance (passed through unchanged), a mongoengine field instance, or a string naming an attribute on model.

Parameters:

Name Type Description Default
fields Sequence[Any]

The field specs to convert.

required
model type[Document]

The document class fields entries are resolved against when given as attribute-name strings.

required

Returns:

Type Description
Sequence[BaseField]

The converted admin fields, in the same order as fields.

Raises:

Type Description
ValueError

If a string entry does not name an attribute on model.

Source code in starlette_admin/contrib/mongoengine/converters.py
def convert_fields_list(
    self,
    *,
    fields: Sequence[Any],
    model: type[me.Document],
    **kwargs: Any,
) -> Sequence[sa.BaseField]:
    """Convert a mixed list of field specs into a list of admin fields.

    Each entry in `fields` may already be a `sa.BaseField` instance (passed
    through unchanged), a mongoengine field instance, or a string naming an
    attribute on `model`.

    Parameters:
        fields: The field specs to convert.
        model: The document class `fields` entries are resolved against
            when given as attribute-name strings.

    Returns:
        The converted admin fields, in the same order as `fields`.

    Raises:
        ValueError: If a string entry does not name an attribute on `model`.
    """
    converted_fields = []
    for value in fields:
        if isinstance(value, sa.BaseField):
            converted_fields.append(value)
        else:
            if isinstance(value, me.BaseField):
                field = value
            elif isinstance(value, str) and hasattr(model, value):
                field = getattr(model, value)
            else:
                raise ValueError(f"Can't find field with key {value}")
            converted_fields.append(self.convert(field=field))
    return converted_fields

get_converter(field)

Look up the converter function registered for field's type.

Tries an exact class match first, then falls back to the first registered class that field is an instance of, so a converter registered for a base mongoengine field type also matches its subclasses.

Raises:

Type Description
NotSupportedField

If no registered converter matches field's type.

Source code in starlette_admin/contrib/mongoengine/converters.py
def get_converter(self, field: me.BaseField) -> Callable[..., sa.BaseField]:
    """Look up the converter function registered for `field`'s type.

    Tries an exact class match first, then falls back to the first
    registered class that `field` is an instance of, so a converter
    registered for a base mongoengine field type also matches its subclasses.

    Raises:
        NotSupportedField: If no registered converter matches `field`'s type.
    """
    if field.__class__ in self.converters:
        return self.converters[field.__class__]
    for cls, converter in self.converters.items():
        if isinstance(field, cls):
            return converter
    raise NotSupportedField(
        f"Field {field.__class__.__name__} can not be converted automatically. Find the appropriate field "
        "manually or provide your custom converter"
    )

starlette_admin.contrib.mongoengine.converters.ModelConverter

Bases: BaseMongoEngineModelConverter

Default converter mapping mongoengine field types to starlette_admin fields.

Source code in starlette_admin/contrib/mongoengine/converters.py
class ModelConverter(BaseMongoEngineModelConverter):
    """Default converter mapping mongoengine field types to `starlette_admin` fields."""

    @classmethod
    def _extract_default(cls, field: me.BaseField) -> Any:
        """Return a Python-usable default value from a mongoengine field.

        Primary keys are excluded since they are auto-generated and are not
        pre-filled in create forms. Both scalar defaults (e.g. ``default=0``)
        and callable defaults (e.g. ``default=datetime.utcnow`` or
        ``default=list``) are supported, as `BaseField.default` already
        accepts either.
        """
        if getattr(field, "primary_key", False):
            return None
        return field.default

    @classmethod
    def _field_common(cls, *, field: me.BaseField, **kwargs: Any) -> dict[str, Any]:
        """Return the kwargs shared by every field conversion: name, help text,
        required flag, and default value.
        """
        return {
            "name": field.name,
            "help_text": getattr(field, "help_text", None),
            "required": field.required,
            "default": cls._extract_default(field),
        }

    @classmethod
    def _numeric_field_common(
        cls, *, field: me.BaseField, **kwargs: Any
    ) -> dict[str, Any]:
        """Return the `min`/`max` kwargs for numeric field conversions, read from
        mongoengine's `min_value`/`max_value` validators.
        """
        return {
            "min": getattr(field, "min_value", None),
            "max": getattr(field, "max_value", None),
        }

    @converts(me.StringField)
    def conv_string_field(self, *args: Any, **kwargs: Any) -> sa.BaseField:
        return sa.StringField(**self._field_common(*args, **kwargs))

    @converts(me.UUIDField)
    def conv_uuid_field(self, *args: Any, **kwargs: Any) -> sa.BaseField:
        return sa.UUIDField(**self._field_common(*args, **kwargs))

    @converts(me.ObjectIdField)
    def conv_object_id_field(self, *args: Any, **kwargs: Any) -> sa.BaseField:
        return internal_fields.ObjectIdField(**self._field_common(*args, **kwargs))

    @converts(me.IntField, me.LongField)
    def conv_int_field(self, *args: Any, **kwargs: Any) -> sa.BaseField:
        return sa.IntegerField(
            **self._field_common(*args, **kwargs),
            **self._numeric_field_common(*args, **kwargs),
        )

    @converts(me.FloatField)
    def conv_float_field(self, *args: Any, **kwargs: Any) -> sa.BaseField:
        return sa.FloatField(**self._field_common(*args, **kwargs))

    @converts(me.DecimalField, me.Decimal128Field)
    def conv_decimal_field(self, *args: Any, **kwargs: Any) -> sa.BaseField:
        return sa.DecimalField(
            **self._field_common(*args, **kwargs),
            **self._numeric_field_common(*args, **kwargs),
        )

    @converts(me.BooleanField)
    def conv_boolean_field(self, *args: Any, **kwargs: Any) -> sa.BaseField:
        return sa.BooleanField(**self._field_common(*args, **kwargs))

    @converts(me.DateTimeField, me.ComplexDateTimeField)
    def conv_datetime_field(self, *args: Any, **kwargs: Any) -> sa.BaseField:
        return sa.DateTimeField(**self._field_common(*args, **kwargs))

    @converts(me.DateField)
    def conv_date_field(self, *args: Any, **kwargs: Any) -> sa.BaseField:
        return sa.DateField(**self._field_common(*args, **kwargs))

    @converts(me.EmailField)
    def conv_email_field(self, *args: Any, **kwargs: Any) -> sa.BaseField:
        return sa.EmailField(**self._field_common(*args, **kwargs))

    @converts(me.URLField)
    def conv_url_field(self, *args: Any, **kwargs: Any) -> sa.BaseField:
        return sa.URLField(**self._field_common(*args, **kwargs))

    @converts(me.MapField, me.DictField)
    def conv_map_field(self, *args: Any, **kwargs: Any) -> sa.BaseField:
        return sa.JSONField(**self._field_common(*args, **kwargs))

    @converts(me.FileField)
    def conv_file_field(self, *args: Any, **kwargs: Any) -> sa.BaseField:
        return internal_fields.FileField(**self._field_common(*args, **kwargs))

    @converts(me.ImageField)
    def conv_image_field(self, *args: Any, **kwargs: Any) -> sa.BaseField:
        return internal_fields.ImageField(**self._field_common(*args, **kwargs))

    @converts(me.EnumField)
    def conv_enum_field(self, *args: Any, **kwargs: Any) -> sa.BaseField:
        field = kwargs["field"]
        # mongoengine stores the wrapped Python enum class on the private
        # `_enum_cls` attribute; there is no public accessor for it.
        return sa.EnumField(**self._field_common(*args, **kwargs), enum=field._enum_cls)

    @converts(me.ReferenceField)
    def conv_reference_field(self, *args: Any, **kwargs: Any) -> sa.BaseField:
        """Convert a `ReferenceField` to a `HasOne` relation.

        `document_type_obj` is a class for direct references and a string for
        lazy references (declared before the target class exists); both are
        normalized to the same key slug.
        """
        field = kwargs["field"]
        dtype = field.document_type_obj
        key = slugify_class_name(dtype if isinstance(dtype, str) else dtype.__name__)
        return sa.HasOne(**self._field_common(*args, **kwargs), key=key)

    @converts(me.EmbeddedDocumentField)
    def conv_embedded_document_field(self, *args: Any, **kwargs: Any) -> sa.BaseField:
        """Convert an `EmbeddedDocumentField` to a `CollectionField`.

        Recursively converts each field of the embedded document type, in the
        order declared on that type.
        """
        field = kwargs["field"]
        document_type_obj: me.EmbeddedDocument = field.document_type
        _fields = []
        for _field in document_type_obj._fields_ordered:
            kwargs["field"] = getattr(document_type_obj, _field)
            _fields.append(self.convert(*args, **kwargs))
        return sa.CollectionField(field.name, _fields, field.required)

    @converts(me.ListField, me.SortedListField)
    def conv_list_field(self, *args: Any, **kwargs: Any) -> sa.BaseField:
        """Convert a `ListField`/`SortedListField` based on its inner field type.

        A list of references becomes a `HasMany` relation. A list of dicts or
        maps collapses to a single `JSONField`, since JSON already represents
        nested lists natively. A list of enum values becomes a multi-select
        dropdown over the enum's choices. Everything else is wrapped in a
        generic `ListField` around the converted inner field.

        Raises:
            ValueError: If the `ListField` was declared without an inner `field`.
        """
        field = kwargs["field"]
        if field.field is None:
            raise ValueError(f'ListField "{field.name}" must have field specified')
        if isinstance(
            field.field,
            (me.ReferenceField, me.CachedReferenceField, me.LazyReferenceField),
        ):
            # List of references: a to-many relationship.
            dtype = field.field.document_type_obj
            key = slugify_class_name(
                dtype if isinstance(dtype, str) else dtype.__name__
            )
            return sa.HasMany(**self._field_common(*args, **kwargs), key=key)
        field.field.name = field.name
        kwargs["field"] = field.field
        if isinstance(field.field, (me.DictField, me.MapField)):
            return self.convert(*args, **kwargs)
        if isinstance(field.field, me.EnumField):
            admin_field = self.convert(*args, **kwargs)
            assert isinstance(admin_field, sa.EnumField)
            admin_field.multiple = True
            admin_field.select2 = True
            return admin_field
        return sa.ListField(self.convert(*args, **kwargs), required=field.required)

conv_embedded_document_field(*args, **kwargs)

Convert an EmbeddedDocumentField to a CollectionField.

Recursively converts each field of the embedded document type, in the order declared on that type.

Source code in starlette_admin/contrib/mongoengine/converters.py
@converts(me.EmbeddedDocumentField)
def conv_embedded_document_field(self, *args: Any, **kwargs: Any) -> sa.BaseField:
    """Convert an `EmbeddedDocumentField` to a `CollectionField`.

    Recursively converts each field of the embedded document type, in the
    order declared on that type.
    """
    field = kwargs["field"]
    document_type_obj: me.EmbeddedDocument = field.document_type
    _fields = []
    for _field in document_type_obj._fields_ordered:
        kwargs["field"] = getattr(document_type_obj, _field)
        _fields.append(self.convert(*args, **kwargs))
    return sa.CollectionField(field.name, _fields, field.required)

conv_list_field(*args, **kwargs)

Convert a ListField/SortedListField based on its inner field type.

A list of references becomes a HasMany relation. A list of dicts or maps collapses to a single JSONField, since JSON already represents nested lists natively. A list of enum values becomes a multi-select dropdown over the enum's choices. Everything else is wrapped in a generic ListField around the converted inner field.

Raises:

Type Description
ValueError

If the ListField was declared without an inner field.

Source code in starlette_admin/contrib/mongoengine/converters.py
@converts(me.ListField, me.SortedListField)
def conv_list_field(self, *args: Any, **kwargs: Any) -> sa.BaseField:
    """Convert a `ListField`/`SortedListField` based on its inner field type.

    A list of references becomes a `HasMany` relation. A list of dicts or
    maps collapses to a single `JSONField`, since JSON already represents
    nested lists natively. A list of enum values becomes a multi-select
    dropdown over the enum's choices. Everything else is wrapped in a
    generic `ListField` around the converted inner field.

    Raises:
        ValueError: If the `ListField` was declared without an inner `field`.
    """
    field = kwargs["field"]
    if field.field is None:
        raise ValueError(f'ListField "{field.name}" must have field specified')
    if isinstance(
        field.field,
        (me.ReferenceField, me.CachedReferenceField, me.LazyReferenceField),
    ):
        # List of references: a to-many relationship.
        dtype = field.field.document_type_obj
        key = slugify_class_name(
            dtype if isinstance(dtype, str) else dtype.__name__
        )
        return sa.HasMany(**self._field_common(*args, **kwargs), key=key)
    field.field.name = field.name
    kwargs["field"] = field.field
    if isinstance(field.field, (me.DictField, me.MapField)):
        return self.convert(*args, **kwargs)
    if isinstance(field.field, me.EnumField):
        admin_field = self.convert(*args, **kwargs)
        assert isinstance(admin_field, sa.EnumField)
        admin_field.multiple = True
        admin_field.select2 = True
        return admin_field
    return sa.ListField(self.convert(*args, **kwargs), required=field.required)

conv_reference_field(*args, **kwargs)

Convert a ReferenceField to a HasOne relation.

document_type_obj is a class for direct references and a string for lazy references (declared before the target class exists); both are normalized to the same key slug.

Source code in starlette_admin/contrib/mongoengine/converters.py
@converts(me.ReferenceField)
def conv_reference_field(self, *args: Any, **kwargs: Any) -> sa.BaseField:
    """Convert a `ReferenceField` to a `HasOne` relation.

    `document_type_obj` is a class for direct references and a string for
    lazy references (declared before the target class exists); both are
    normalized to the same key slug.
    """
    field = kwargs["field"]
    dtype = field.document_type_obj
    key = slugify_class_name(dtype if isinstance(dtype, str) else dtype.__name__)
    return sa.HasOne(**self._field_common(*args, **kwargs), key=key)

Exceptions

starlette_admin.contrib.mongoengine.exceptions.NotSupportedField

Bases: StarletteAdminException

Source code in starlette_admin/contrib/mongoengine/exceptions.py
class NotSupportedField(StarletteAdminException):
    pass

Note

Concrete filter classes (EqualFilter, ArrayInFilter, ObjectIdEqualFilter, and so on) are not enumerated here. They mirror the backend-agnostic filters documented in Filters; MongoEngine-specific behavior is covered in MongoEngine.