ITADN

MongoEngine `QueryAjaxModelLoader.format` crashes (HTTP 500) on unresolved `DBRef` values

#2917Openhasansezertasan 创建于 2026-06-14
H
hasansezertasancommented
## Summary `QueryAjaxModelLoader.format()` in `flask_admin.contrib.mongoengine.ajax` accesses `model.pk` unconditionally. When MongoEngine cannot dereference a `ReferenceField` (e.g. the referenced document has been deleted, or the reference points to a different collection / database), it returns a raw `bson.dbref.DBRef` instead of a `Document`. `DBRef` has no `.pk` attribute, so the AJAX endpoint that powers the admin autocomplete widget crashes with an `AttributeError`, surfacing as **HTTP 500** and breaking the edit form. This was originally reported in #2036 (Sep 2020). The PR there bundled an unrelated `Content-Disposition` fix, went stale, and now conflicts with `master`. Opening a focused issue so the fix can be discussed and landed on its own. ## Expected behavior When an AJAX reference loader encounters a broken/unresolved reference, the widget should degrade gracefully — either display the `DBRef` id as the label (so the user can see *what* is broken and clear it) or return `None` and let the form render an empty selection — not return a 500. ## Actual behavior ``` AttributeError: 'DBRef' object has no attribute 'pk' ``` …rendered as `500 Internal Server Error` from the `/admin/<view>/ajax/lookup/` endpoint. The edit form's reference field becomes unusable until the broken reference is cleaned up directly in the database. ## Root cause `flask_admin/contrib/mongoengine/ajax.py:52`: ```python def format(self, model: Document | None) -> tuple[str, str] | None: if not model: return None return (as_unicode(model.pk), as_unicode(model)) ``` `model` here can be a `DBRef` when MongoEngine fails to dereference. There is no type-check before `.pk` access. ## Notes on PR #2036 The original PR proposed: ```python from bson.dbref import DBRef def format(self, model): if not model: return None if not isinstance(model, DBRef): return (as_unicode(model.pk), as_unicode(model)) else: return (as_unicode(model), as_unicode(model)) ``` The shape is right, but a few things to settle before landing: 1. `as_unicode(dbref)` returns a string like `DBRef('coll', ObjectId('…'))`, which is not a usable id for the autocomplete value. The first tuple element should probably be `as_unicode(model.id)` (the `ObjectId` the `DBRef` carries) so the form posts back something the persistence layer can resolve. 2. Worth deciding whether the second tuple element (the user-facing label) should be the raw `DBRef` repr or a clearer marker like `"(broken reference)"` — the current `as_unicode(model)` exposes internal types to end users. 3. The import is fine at module level (`bson` is already a transitive dep of `mongoengine`), but a typing-friendly form is preferable. ## Possible fix sketch (for discussion, not part of this issue) ```python from bson.dbref import DBRef def format(self, model: Document | DBRef | None) -> tuple[str, str] | None: if not model: return None if isinstance(model, DBRef): return (as_unicode(model.id), f"(missing: {model.collection}/{model.id})") return (as_unicode(model.pk), as_unicode(model)) ``` …plus a unit test that constructs a `DBRef` pointing at a non-existent document and asserts `format()` returns a 2-tuple instead of raising. ## Reproducer A full MRE requires MongoDB. The minimal path: 1. Define two MongoEngine `Document` classes — `Author` and `Book`, where `Book.author = ReferenceField(Author)`. 2. Register a `ModelView` for `Book` with `form_ajax_refs={"author": {...}}`. 3. Create an `Author`, create a `Book` referencing it, then delete the `Author` document directly (bypassing cascade). 4. Open the `Book` edit view → AJAX call to populate the existing value crashes with the `AttributeError` above. ## Environment - `flask-admin` from `master` (commit at time of writing). - `mongoengine` (any recent version). - MongoDB. ## Related - #2036 — original report and bundled PR (will be closed in favour of two focused PRs). --- > Disclosure: I drafted this issue with help from Claude Code while triaging > stale PR #2036; the references above were verified manually.
0 条评论