Skip to content

0.5.0

Latest

Choose a tag to compare

@igorbenav igorbenav released this 08 Jun 00:24
8b97fb4

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-id primary 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

  1. Relationship display — auto-detected SQLAlchemy relationships, viewable as expandable rows
  2. Foreign-key dropdowns — FK fields render as <select> of related records, labelled by a configurable field
  3. Success feedback — a confirmation banner after create/update
  4. Non-id primary keys — models keyed on job_id, uuid, etc. now work in get/update/delete
  5. Postgres event-logging fix — updates with track_events=True no longer fail on integer-PK models
  6. Session recovery — a database error no longer bricks the admin until restart
  7. Security pass — all Dependabot + CodeQL alerts closed
  8. FastCRUD 0.22.2 + Python 3.10+
  9. Docs on Zensical (replaces MkDocs)
  10. 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 id

When 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-jose removed — it was a declared dependency that nothing imported (the admin uses signed cookie sessions, not JWTs). Removing it eliminated the ecdsa (Minerva timing, which has no fixed release), pyasn1, and rsa advisories outright.
  • Vulnerable packages patchedpython-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.

  1. Be on Python 3.10+. This is the one hard requirement. On 3.9, either upgrade Python or stay pinned to v0.4.3.
  2. Upgrade the package: pip install -U crudadmin (or uv add crudadmin@latest). FastCRUD will move to >=0.22.2 automatically; if you separately pin an older FastCRUD, unpin it.
  3. MySQL users: the mysql extra now needs aiomysql>=0.3.0.
  4. If you imported python-jose through CRUDAdmin, declare it as your own dependency — CRUDAdmin no longer installs it.
  5. Optional — set display_field on your add_view() calls so related records show a name instead of an id.
  6. 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-id primary 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