Release v0.5.0 - Relationships, Reliability, and a Security Pass
Release Notes by the CRUDAdmin team
This is the first release since v0.4.3, and it bundles a lot of work that's been landing on main: a relationship-aware admin, a batch of correctness fixes that were biting real deployments, a full security sweep of the dependency tree and the source, a move onto FastCRUD 0.22, and a docs toolchain migration to Zensical.
There is one breaking change that matters to everyone: CRUDAdmin now requires Python 3.10+. The rest are either opt-in features or fixes that only change behavior you were probably already unhappy with. None of it is a "rewrite your app" upgrade — for most projects this is a pip install -U followed by making sure you're on Python 3.10 or newer.
Why Python 3.9 had to go
CRUDAdmin builds on FastCRUD, and FastCRUD 0.21 dropped Python 3.9 (it reached end-of-life in October 2025). To pick up FastCRUD's native relationship detection — which the new relationship features in this release are built on — we had to move to FastCRUD 0.22, and that means 3.10 is now the floor here too. If you're pinned to 3.9, stay on v0.4.3; we'll keep that tag around.
The shape of this release
Three things drove it:
- Relationships became first-class. Foreign keys now render as dropdowns of real records, and related rows are viewable inline — instead of typing raw ids and guessing.
- The bug reports got addressed. Non-
idprimary keys, a Postgres-only update failure that looked like an auth bug, and a session that got permanently poisoned by a single duplicate-key error — all fixed, all with regression tests. - Security got a real pass. Every open Dependabot and CodeQL alert is closed: an unused crypto dependency removed, vulnerable packages patched, error responses stopped leaking internals, a weak hash replaced, and CI tokens locked down.
What's New in v0.5.0
- Relationship display — auto-detected SQLAlchemy relationships, viewable as expandable rows
- Foreign-key dropdowns — FK fields render as
<select>of related records, labelled by a configurable field - Success feedback — a confirmation banner after create/update
- Non-
idprimary keys — models keyed onjob_id,uuid, etc. now work in get/update/delete - Postgres event-logging fix — updates with
track_events=Trueno longer fail on integer-PK models - Session recovery — a database error no longer bricks the admin until restart
- Security pass — all Dependabot + CodeQL alerts closed
- FastCRUD 0.22.2 + Python 3.10+
- Docs on Zensical (replaces MkDocs)
crudadmin.__version__is now exposed
Breaking Changes Summary
| Change | Impact | Migration Effort |
|---|---|---|
| Python 3.9 dropped (now 3.10+) | Won't install on 3.9 | High if on 3.9 — upgrade Python |
FastCRUD >=0.22.2 (was >=0.15.12) |
Conflicts if you pin an older FastCRUD | Low — unpin / upgrade |
python-jose removed from dependencies |
Breaks only code that imported it transitively | Low — declare it yourself if needed |
aiomysql>=0.3.0 for the mysql extra |
Must upgrade aiomysql | Low |
| FK fields now render as dropdowns | Can't free-type an id; dropdown caps at 100 options | Low |
| Admin error responses are now generic | Clients parsing the exception text in error bodies | Low |
| Docs: MkDocs → Zensical | mkdocs build/serve gone |
Low — for fork/docs maintainers only |
Detailed Changes
1. Relationship display
Previously the admin ignored SQLAlchemy relationships entirely — a Book with an author relationship showed author_id as a number and nothing more. CRUDAdmin now detects relationships (delegating to FastCRUD's native relationship detection) and surfaces them in the list view as expandable rows: expand a Book to see its Author; expand an Author to see its Books.
The label shown for a related record is explicit, not guessed. You declare it per model when registering the view:
admin.add_view(model=Author, create_schema=AuthorCreate, update_schema=AuthorUpdate,
display_field="name") # Book -> author shows the name, not the idWhen a related model has no configured display_field, the label falls back to its primary key. Relationship types (BelongsTo, HasOne, HasMany, ManyToMany) are detected automatically and rendered with the appropriate component.
2. Foreign-key dropdowns
Foreign-key fields on create/update forms used to be plain integer inputs — you typed the related record's id from memory. They now render as a <select> populated with the related rows, labelled by the related model's display_field (falling back to its primary key). The dropdown lists up to 100 related records; relationships to very large tables that need more than that are a known limitation for now.
3. Success feedback
Creating or updating a record redirected to the list view with no confirmation, which led at least one user to assume a successful action had failed and retry it. Create and update now redirect with a ?success=… marker, and the list view renders a dismissible success banner ("… created/updated successfully").
4. Non-id primary keys ⚠️ BREAKING (bug fix)
The get/update/delete paths hardcoded id as the lookup column, so a model whose primary key was named anything else failed on edit with Invalid column 'id' for model …. The lookup now uses the model's actual primary-key column name throughout.
class DeploymentJob(Base):
__tablename__ = "deployment_jobs"
job_id = Column(Integer, primary_key=True) # now works in the admin's get/update/delete
name = Column(String)Note: a model with no primary key is now rejected at add_view() time with a clear error, rather than failing later at query time.
5. Postgres event-logging fix
With track_events=True, the event decorator fetched a record's state by passing the raw URL string straight into the query. On SQLite that's fine; on Postgres, asyncpg binds the string as VARCHAR and the engine rejects integer = character varying, producing a 500 that the admin surfaced as a redirect to /admin/login?error=… — so it looked like an authentication failure on every update of any integer-PK model. The identifier is now converted to the primary-key type before the lookup.
6. Session recovery
get_admin_db handed every request the same long-lived AsyncSession and committed it with no rollback path. A single failed statement — e.g. creating an admin user with a duplicate username — left that shared session in a broken state, and since it was reused by every later request, the entire admin returned the same traceback until the process was restarted.
The admin database session is now created per request with rollback-on-error, and the create/update handlers roll back on a caught database error. A duplicate-key submission now shows a form error and the admin keeps working.
7. Security pass
Every open Dependabot and CodeQL alert is closed:
python-joseremoved — it was a declared dependency that nothing imported (the admin uses signed cookie sessions, not JWTs). Removing it eliminated theecdsa(Minerva timing, which has no fixed release),pyasn1, andrsaadvisories outright.- Vulnerable packages patched —
python-multipart,urllib3,aiomysql,starlette,idna,python-dotenv,filelock,requests, and others bumped to patched releases. - Error responses no longer leak internals — admin error responses log the exception server-side and return a generic message instead of echoing
str(e). - Weak hash replaced — the Memcached backend uses SHA-256 instead of MD5 for its cache-key hashing.
- CI hardened — all workflows declare least-privilege
permissions: contents: read.
8. FastCRUD 0.22.2 + Python 3.10+ ⚠️ BREAKING
The FastCRUD pin moved from >=0.15.12 to >=0.22.2. FastCRUD 0.20 changed create() to return None unless schema_to_select is given; CRUDAdmin's internal call sites were updated accordingly, and the deprecated Pydantic v1 APIs (__fields__, .dict()) were replaced with their v2 equivalents along the way. The supported Python range is now 3.10–3.13.
9. Docs on Zensical
The documentation site moved from Material for MkDocs to Zensical, configured via zensical.toml. mkdocs.yml is gone. Preview locally with uv run zensical serve; build with uv run zensical build. A GitHub Pages deploy workflow ships in this release (there wasn't one before). Note: per-page auto meta descriptions and Google Analytics were not carried over.
10. crudadmin.__version__
The installed version is now available at runtime, read from package metadata:
import crudadmin
crudadmin.__version__ # "0.5.0"Migration Guide
For most projects this is a straightforward upgrade.
- Be on Python 3.10+. This is the one hard requirement. On 3.9, either upgrade Python or stay pinned to
v0.4.3. - Upgrade the package:
pip install -U crudadmin(oruv add crudadmin@latest). FastCRUD will move to>=0.22.2automatically; if you separately pin an older FastCRUD, unpin it. - MySQL users: the
mysqlextra now needsaiomysql>=0.3.0. - If you imported
python-josethrough CRUDAdmin, declare it as your own dependency — CRUDAdmin no longer installs it. - Optional — set
display_fieldon youradd_view()calls so related records show a name instead of an id. - Nothing to regenerate: sessions, admin users, and event/audit tables are unchanged. No data migration is required.
For fork maintainers publishing their own docs: replace mkdocs build/mkdocs gh-deploy with uv run zensical build and the Pages workflow shipped in .github/workflows/docs.yml.
What's Changed
- Add relationship detection and display support (#63) — relationship display on native FastCRUD with explicit
display_field; FastCRUD>=0.22.2; Python 3.9 dropped - Fix admin issues (#73) — non-
idprimary keys (#68), Postgres event-logging (#72), session recovery (#65), foreign-key dropdowns (#53), success feedback (#71), and a Pydantic v2 cleanup - Security alerts (#75) — drop unused
python-jose, patch vulnerable dependencies, stop leaking exception details, SHA-256 for memcached keys, lock down CI workflow permissions - Migrate documentation from MkDocs to Zensical (#76)
- Expose
crudadmin.__version__and bump to 0.5.0
Full Changelog: v0.4.3...v0.5.0