Skip to content

docs(inline): correct inline post-processing example and fix Peewee parity (#1738)#2901

Open
jbbqqf wants to merge 4 commits into
pallets-eco:masterfrom
jbbqqf:docs/1738-inline-postprocess-form
Open

docs(inline): correct inline post-processing example and fix Peewee parity (#1738)#2901
jbbqqf wants to merge 4 commits into
pallets-eco:masterfrom
jbbqqf:docs/1738-inline-postprocess-form

Conversation

@jbbqqf
Copy link
Copy Markdown

@jbbqqf jbbqqf commented May 22, 2026

Summary

The inline_model_form_converter docstrings in
flask_admin.contrib.sqla.view and flask_admin.contrib.peewee.view
advertise a post_process method on a converter subclass. That method
name is never invoked anywhere in the codebase, so following the
documented example is silently a no-op. The hook the SQLAlchemy
converter actually calls is InlineFormAdmin.postprocess_form (see
flask_admin/contrib/sqla/form.py:1014,1139); the examples/sqla_custom_inline_forms/main.py:132
example uses that real hook.

Fixes #1738.

Context

  • flask_admin/contrib/sqla/view.py:213-224 (pre-change) and
    flask_admin/contrib/peewee/view.py:112-123 (pre-change) showed:

    class MyInlineModelConverter(InlineModelConverter):
        def post_process(self, form_class, info):
            ...

    Grepping the repo for post_process returns only those two docstring
    occurrences -- nothing in the code path ever calls a method by that
    name.

  • The real contract is on InlineBaseFormAdmin:
    flask_admin/model/form.py:120-134 defines
    postprocess_form(self, form_class) and the SQLA converter invokes it
    at flask_admin/contrib/sqla/form.py:1014 and :1139.

  • The Peewee inline converter (flask_admin/contrib/peewee/form.py:287-350)
    never invoked postprocess_form at all, so the documented pattern was
    effectively broken on the Peewee backend as well.

Changes

  • flask_admin/contrib/sqla/view.py, flask_admin/contrib/peewee/view.py:
    rewrite the inline_model_form_converter docstring example to use
    InlineFormAdmin.postprocess_form (the hook the converter actually
    invokes) and to pass the subclass through inline_models.
  • flask_admin/contrib/peewee/form.py: call
    info.postprocess_form(child_form) inside
    InlineModelConverter.contribute, mirroring the long-standing SQLA
    behavior so the documented hook works on both backends.
  • flask_admin/tests/sqla/test_inlineform.py:
    add test_inline_form_postprocess_form_hook that subclasses
    InlineFormAdmin, contributes an extra StringField via
    postprocess_form, and asserts the field lands on the generated
    inline form class. Pins the documented contract.
  • flask_admin/tests/peeweemodel/test_basic.py:
    add a matching regression test that fails on origin/main (the hook
    was never called) and passes on this branch.
  • doc/changelog.rst: add [unreleased] entries for the documentation
    fix and the Peewee bug fix.

Reproduce BEFORE/AFTER yourself (copy-paste)

# --- one-time setup ---
git clone https://github.com/pallets-eco/flask-admin.git /tmp/fa-1738 && cd /tmp/fa-1738
uv venv .venv --python 3.12
source .venv/bin/activate
uv pip install -e ".[sqlalchemy,peewee]" pytest flask-sqlalchemy-lite

# --- BEFORE: origin/master, Peewee `postprocess_form` hook is never called ---
git checkout origin/master
git fetch https://github.com/jbbqqf/flask-admin.git docs/1738-inline-postprocess-form
git checkout FETCH_HEAD -- flask_admin/tests/peeweemodel/test_basic.py
pytest flask_admin/tests/peeweemodel/test_basic.py::test_inline_form_postprocess_form_hook -v
# Expected: FAILED -- "InlineFormAdmin.postprocess_form should contribute
#                      extra fields on the Peewee backend just like it
#                      does on the SQLAlchemy backend."
git checkout origin/master -- flask_admin/tests/peeweemodel/test_basic.py

# --- AFTER: this branch, both SQLA and Peewee fire the documented hook ---
git checkout FETCH_HEAD
pytest \
  flask_admin/tests/peeweemodel/test_basic.py::test_inline_form_postprocess_form_hook \
  flask_admin/tests/sqla/test_inlineform.py::test_inline_form_postprocess_form_hook \
  -v
# Expected: 4 passed, 1 skipped (SQLALite-with-session_deprecated is skipped
#           by the conftest fixture; the SQLA + Peewee cases all pass).

What I ran locally

  • New tests, focused:
    pytest flask_admin/tests/peeweemodel/test_basic.py::test_inline_form_postprocess_form_hook \
           flask_admin/tests/sqla/test_inlineform.py::test_inline_form_postprocess_form_hook -v
    # 4 passed, 1 skipped
    
  • Whole inline test file:
    pytest flask_admin/tests/sqla/test_inlineform.py -q
    # 18 passed, 6 skipped
    
  • Broader regression sweep (excluding the Postgres-only test module, which
    needs psycopg2):
    pytest flask_admin/tests/peeweemodel/ flask_admin/tests/sqla/ \
           flask_admin/tests/test_base.py flask_admin/tests/test_model.py \
           --ignore=flask_admin/tests/sqla/test_postgres.py -q
    # 335 passed, 81 skipped, 13 xfailed
    
  • Lint:
    ruff check flask_admin/contrib/peewee/form.py flask_admin/contrib/peewee/view.py \
               flask_admin/contrib/sqla/view.py \
               flask_admin/tests/peeweemodel/test_basic.py \
               flask_admin/tests/sqla/test_inlineform.py
    # All checks passed!
    

Edge cases

# Scenario Input Expected Verified by
1 SQLA backend, subclass InlineFormAdmin and contribute extra field via postprocess_form inline_models = (UserInfoInlineForm(UserInfo),) view._create_form_class.info.args[0] has extra attribute new test_inline_form_postprocess_form_hook[SQLAProvider-*]
2 Peewee backend, same subclass-and-contribute pattern inline_models = (Model2InlineForm(Model2),) generated inline form class has extra attribute new test_inline_form_postprocess_form_hook (peewee)
3 Plain inline_models = (Model,) with no InlineFormAdmin subclass existing behavior continues to work unchanged existing test_inline_form / test_inline_form_required / test_inline_form_self / test_inline_form_base_class all still pass
4 Peewee InlineFormAdmin returning a custom form via get_form() (skips the auto-generated path) info.get_form() returns non-None still passes through postprocess_form after assignment code path now uniformly calls postprocess_form after the if child_form is None branch, matching the SQLA backend's structure

Risk / blast radius

  • Docstring-only change for SQLA: no behavior change.
  • Peewee InlineModelConverter.contribute: now calls
    info.postprocess_form(child_form). The default
    InlineFormAdmin.postprocess_form (flask_admin/model/form.py:134)
    returns the form class unchanged, so existing call sites that never
    overrode the hook see no diff. Behavior changes only for users who
    do override the hook -- which is the previously-documented but
    broken case we're fixing.
  • All non-Postgres tests pass locally; the Postgres test module fails
    to import only because psycopg2 isn't installed in my dev env (it's
    unrelated to this diff).

Release note

Fix the SQLAlchemy/Peewee `inline_model_form_converter` docstring example
to point at the real `InlineFormAdmin.postprocess_form` hook, and wire
the Peewee backend to call that hook so the documented pattern actually
works there.

PR drafted with assistance from Claude Code (Anthropic). The change was
reviewed manually against flask-admin's source -- file:line references in
the body were checked against the cloned tree, and the copy-paste
BEFORE/AFTER reproducer block is the one I used during development;
reviewers can paste it verbatim.

Jean-Baptiste Braun and others added 4 commits May 22, 2026 22:24
…arity (pallets-eco#1738)

The `inline_model_form_converter` docstrings in
`flask_admin.contrib.sqla.view` and `flask_admin.contrib.peewee.view`
advertised a `post_process` method on a converter subclass. That method
name is never invoked anywhere in the codebase, so following the
example is silently a no-op. The hook actually called by the SQLA
converter is `InlineFormAdmin.postprocess_form` (see
`flask_admin/contrib/sqla/form.py:1014,1139`).

Update both docstrings to show the real hook, and add a regression test
in `flask_admin/tests/sqla/test_inlineform.py` that subclasses
`InlineFormAdmin`, contributes an extra field via `postprocess_form`,
and asserts it lands on the generated inline form class.

The Peewee inline converter never wired `postprocess_form` at all, so
the same documented pattern was effectively broken there as well. Call
`info.postprocess_form(child_form)` from
`InlineModelConverter.contribute` to match the SQLA backend, and add a
matching regression test in `flask_admin/tests/peeweemodel/test_basic.py`.

Closes pallets-eco#1738.
CI's `typing` job (mypy in strict mode) flagged three things on the
previous commit:

1. `flask_admin/contrib/peewee/form.py:334` -- the type: ignore on the
   `info.postprocess_form(child_form)` call only suppressed
   `[attr-defined]` but mypy now also raises `[arg-type]` (Any vs
   BaseForm) and `[assignment]` (BaseForm assigned into a slot mypy
   types as `BaseModelView | None`). Switch the comment to
   `[arg-type,assignment]` so the existing intent is preserved without
   the now-unused attr-defined ignore.

2. `flask_admin/tests/sqla/test_inlineform.py:404` -- `# type: ignore[no-untyped-def]`
   is unused; per project convention `disallow_untyped_defs` is off for
   test files, so just drop the ignore.

3. `flask_admin/tests/peeweemodel/test_basic.py:1199` -- same thing.

4. `flask_admin/tests/peeweemodel/test_basic.py:1210` --
   `create_form_cls.model2_set` is a dynamically-named backref, so add
   `# type: ignore[attr-defined]` to mute the attr-defined error.

Verified locally:
- `mypy --python-version 3.10 flask_admin/contrib/peewee/form.py flask_admin/tests/sqla/test_inlineform.py flask_admin/tests/peeweemodel/test_basic.py` -> Success.
- `pytest flask_admin/tests/peeweemodel/test_basic.py::test_inline_form_postprocess_form_hook flask_admin/tests/sqla/test_inlineform.py -q` -> 19 passed, 6 skipped.
@ElLorans ElLorans force-pushed the docs/1738-inline-postprocess-form branch from 676355e to 909b05c Compare May 22, 2026 21:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

Incorrect documentation for post-processing inline forms

2 participants