Flaxon 0.3.0 and Teloce-Py 0.3.0 are available on PyPI. Release instructions are maintained on this site; source links identify the framework snapshot. Check version and upgrade guidance.
Admin and CMS Production Guide
For generated projects, use the shared settings.py, Flaxon.from_settings(),
explicit mounts in app.py, and management.py workflow. See ORM and project management
for Python migrations, model registration, permission hooks, relationship editing,
and Admin/CMS metadata storage. Backend-only and legacy adapter examples remain supported.
Flaxon provides two complementary management surfaces:
AdminDashboardmanages application models, users, roles, settings, media, audit activity, search, and operational status.CMSprovides schema-driven content editing through a browser SPA, including publishing workflows, revisions, taxonomies, comments, menus, and data import/export.
The bundled UI is a reference client. Every CMS action is also available as a JSON API, so a team can replace the SPA with React, Vue, mobile, or server-side clients without replacing the backend contracts.
Quick Start
Install the Admin extra:
pip install "flaxon[admin]"
import os
from flaxon import Flaxon
from flaxon.admin import AdminConfig, AdminDashboard
from flaxon.admin.cms import CMS
app = Flaxon("backoffice", debug=True)
admin = AdminDashboard(
app,
config=AdminConfig(site_title="Acme Backoffice", timezone="UTC"),
url_prefix="/admin",
storage_path="admin.sqlite3",
users=[{"username": "admin", "password": os.environ["FLAXON_ADMIN_PASSWORD"]}],
)
cms = CMS(app, url_prefix="/admin/cms", title="Acme Content", auth=admin.auth)
Run locally:
flaxon run app:app --reload --port 8000
Set FLAXON_ADMIN_PASSWORD through your environment before starting this standalone example. Its programmatic users registration still enforces the Admin password validator. No default password is provided. Use /admin/login, /admin/, and /admin/cms/.
Generated-project Admin setup
For the generated settings.py/management.py workflow, create staff credentials interactively instead of putting passwords in application code:
python management.py setup-admin
# Equivalent alias:
# python management.py createsuperuser
python management.py runserver
Both setup commands accept a nonempty password up to 128 characters, with matching confirmation. DEBUG=True accepts weak passwords quietly. DEBUG=False recommends a long, unique production password when the chosen password fails the strength checks but still creates the account. Passwords are hidden during entry and securely hashed in storage. Existing usernames are never overwritten. Web Admin account creation, password changes and resets retain their existing validator. Restart an already running server after command-line account creation.
See project setup and the recording course.
Persistence and Migrations
For a self-contained local store, set storage_path (the parent directory must
already exist). This persists users,
roles, settings, activity, media metadata, CMS records, taxonomies, comments,
menus, and revisions in the admin store. Do not use the default in-memory mode
for production or multiple workers.
For an application-owned database, generate and apply the migration:
from flaxon.admin import write_admin_migration
write_admin_migration("migrations")
flaxon migrate --database sqlite://./app.db --migrations-dir migrations
Pass the configured DatabaseManager as database= to both AdminDashboard
and CMS. The framework keeps the admin/CMS namespace contract separate from
your domain repositories, so existing application tables are not modified.
For PostgreSQL, MySQL, SQLAlchemy, or a custom repository, implement the
database methods used by the services (execute, fetch_one, and
fetch_all) and pass that object as database=. Keep writes transactional.
Authentication and Authorization
Admin routes are authenticated by the dashboard session backend. Use an application-owned backend when the application already has identity:
admin = AdminDashboard(app, auth_backend=my_auth_backend)
cms = CMS(app, auth=admin.auth)
Permissions are checked at the route boundary. Common permissions are:
| Permission | Scope |
|---|---|
admin:read |
Dashboard, model lists, activity, operations |
admin:write |
Model create/update/actions |
admin:users |
User and role management |
admin:media |
Upload and manage assets |
admin:settings |
Change site settings |
<model>:create |
Create a specific model |
<model>:read |
Read a specific model |
<model>:update |
Update a specific model |
<model>:delete |
Delete a specific model |
admin:superuser |
Full access |
Roles map to permissions through the role editor or admin.roles. Do not
grant admin:superuser to normal editors. Use separate editor and publisher
roles when content approval is required.
Security options include login throttling, CSRF validation, TOTP MFA, single-use password reset tokens, optional email verification, file type and size validation, path traversal protection, and rich-text allowlist sanitization.
admin = AdminDashboard(
app,
require_email_verification=True,
password_reset_sender=send_reset_message,
email_verification_sender=send_verification_message,
max_upload_size=10 * 1024 * 1024,
allowed_upload_types={"image/jpeg", "image/png", "image/webp", "application/pdf"},
)
Browser forms include a hidden _csrf field. SPA mutations include the same
token in the X-CSRF-Token header. Custom clients must preserve this rule.
The backend exposes revision comparison data, menu hierarchy data, media metadata operations, scheduled status fields, durable thumbnail jobs, resumable uploads, audit verification, and notification preferences. The bundled SPA is the reference client for the core workflows; applications needing a full editorial calendar, visual diff workspace, nested menu tree editor, or external notification delivery should build those views and adapters on these APIs.
Model Admin
Register a model with async CRUD hooks:
from flaxon.admin import admin_model
@admin_model(
list_display=["id", "name", "price"],
list_filter=["active"],
search_fields=["name", "sku"],
fields=["name", "sku", "price", "active"],
)
class Product:
_items = {}
@classmethod
async def get_instances(cls): return list(cls._items.values())
@classmethod
async def get_instance(cls, id): return cls._items.get(id)
@classmethod
async def create_instance(cls, data):
data["id"] = str(len(cls._items) + 1)
cls._items[data["id"]] = data
return data
@classmethod
async def update_instance(cls, id, data):
if id not in cls._items: return None
cls._items[id].update(data)
return cls._items[id]
@classmethod
async def delete_instance(cls, id):
return cls._items.pop(id, None) is not None
The model list supports search, exact filters, sorting, pagination, selection,
bulk actions, create, edit, detail, delete confirmation, and history links.
Use admin.register(model, ...) when decorator registration is not suitable.
admin_action marks reusable action methods; attach custom callables to the
registered AdminModel when an action needs custom labels or a separate
service:
async def archive(ids):
for record_id in ids:
await Product.update_instance(record_id, {"active": False})
admin.registry.get("product").add_action("archive", archive)
Use admin_display for computed read-only columns. Keep authorization in the
dashboard permission map and business validation in the model/service layer.
CMS Content Types
from flaxon.admin.cms import CMSField, ContentType
cms.register(ContentType(
name="post",
label="Post",
label_plural="Posts",
fields=[
CMSField("title", required=True),
CMSField("summary", type="textarea"),
CMSField("body", type="richtext"),
CMSField("hero", type="image"),
CMSField("published_on", type="datetime"),
CMSField("related", type="relationship"),
CMSField("blocks", type="repeater"),
CMSField("seo", type="json"),
],
list_display=["title", "status", "updated_at"],
list_filter=["status"],
search_fields=["title", "summary", "body"],
statuses=["draft", "review", "approved", "scheduled", "published", "archived"],
))
Supported field types are text, textarea, richtext, boolean, number,
date, datetime, email, url, select, json, repeater,
relationship, file, and image. Required fields and select choices are
validated by the content type. Slugs are generated from slug_source unless
explicitly supplied.
The SPA provides list search/filter/sort/pagination, bulk publish/unpublish, draft autosave, unsaved-change warnings, status and schedule controls, media fields, revision history, import/export, comments, taxonomy management, and a drag-and-drop menu editor.
Editorial Workflows
Use the status field as the editorial state machine. A typical workflow is:
draft -> review -> approved -> scheduled -> published -> archived
The backend validates configured statuses. Records with a future publish_at
are treated as scheduled until the next query after their publish time. For
approval rules, register hooks or enforce transitions in a service before
calling ContentType.update.
def audit_content(record):
return record
cms.add_hook("after_update", audit_content)
Each create, update, delete, and restore creates a revision. The history API returns revision metadata, before/after values, changed fields, and restore operations. Treat revision payloads as audit data and restrict access to trusted editors.
Taxonomies, Comments, and Menus
The SPA exposes these resources from the CMS header. They are also JSON APIs:
- Taxonomies: create/delete a taxonomy and add/update/delete terms.
- Comments: list, create, approve, reject, mark spam, and delete comments.
- Menus: read and replace ordered menu items, including nested
children.
Applications should validate public comment input, add anti-spam controls, and only expose approved comments on the public site. Menu item URLs should be validated before rendering into a public navigation.
Media
The admin media library supports upload, folders, previews/URLs, MIME and size
validation, metadata editing, rename, and delete. Configure a durable upload
directory or replace FileStorage with object storage. Never trust a client
filename or content type; retain the allowlist and size limit at the server.
For a custom picker, use the media API and store the returned stable URL or
asset identifier in a CMS image or file field. Do not store arbitrary
client-provided paths.
JSON API
With url_prefix="/admin/cms", the SPA API is:
| Method | Path | Purpose |
|---|---|---|
| GET | /api/config |
Content schemas and UI metadata |
| GET | /api/stats |
Per-type counts |
| GET | /api/<type>/items |
Search/filter/sort/paginate records |
| POST | /api/<type>/items |
Create |
| GET | /api/<type>/items/<id> |
Read |
| PUT | /api/<type>/items/<id> |
Update |
| DELETE | /api/<type>/items/<id> |
Delete |
| GET | /api/<type>/items/<id>/history |
Revisions and comparisons |
| POST | /api/<type>/items/<id>/restore/<revision> |
Restore revision |
| POST | /api/<type>/actions/<action> |
Bulk action |
| GET | /api/export/<type> |
JSON or CSV export |
| POST | /api/import/<type> |
JSON/CSV import with row errors |
| GET/POST | /api/taxonomies |
Taxonomy collection |
| GET/PATCH/DELETE | /api/taxonomies/<name> |
Taxonomy terms |
| GET/POST | /api/comments |
Comment queue |
| PATCH/DELETE | /api/comments/<id> |
Moderate/delete comment |
| GET/PUT | /api/menus/<name> |
Menu management |
All mutating API calls require X-CSRF-Token and an authenticated session.
Use the response errors array from imports to display row-level validation
feedback. Limit per_page to the server-supported maximum.
Custom UI and Extension Points
The SPA is intentionally replaceable. A custom client should:
- Fetch
/api/configand generate fields from the schema. - Preserve CSRF and session cookies.
- Render explicit loading, empty, validation, conflict, and permission states.
- Use history before destructive restore operations.
- Subscribe to Redis-backed events when running multiple workers.
Backend extensions include AdminDashboard.register_widget, dashboard hooks,
CMS hooks (before_create, after_create, before_update, after_update,
before_delete, after_delete, and restore hooks), custom model actions,
custom templates, AuthenticationBackend, storage adapters, and Redis session
and WebSocket broadcasting.
Production Checklist
- Apply admin/CMS migrations through
flaxon migrate. - Use PostgreSQL/MySQL or a durable SQLite deployment; do not use in-memory state.
- Set a strong secret and unique admin passwords through secret management.
- Keep CSRF enabled and serve admin over HTTPS.
- Configure login rate limits, MFA, reset delivery, and email verification.
- Use Redis or a shared database session backend for multiple workers.
- Configure a durable media/object-storage backend and strict MIME limits.
- Add public-site comment moderation and spam protection.
- Restrict rich text allowlists to the tags and attributes your renderer needs.
- Export and retain audit activity and test restore procedures.
- Run API tests plus browser tests for login, create/edit, publishing, media, moderation, import/export, and permission boundaries.
The complete runnable reference is
docs/examples/cms/full_admin_cms/app.py.
Production Addendum
The following configuration enables the hardened Admin/CMS path without changing the existing registration code:
admin = AdminDashboard(
app,
storage_path="var/admin.sqlite3",
redis_url="redis://localhost:6379/0",
redis_protocol=2,
redis_max_connections=100,
session_idle_timeout=1800,
media_scanner=clamav_scan,
)
cms = CMS(app, auth=admin.auth, redis_url="redis://localhost:6379/0")
Use Redis for every web and worker process. It coordinates sessions, Admin
rate limits, CMS publishing locks, and WebSocket events. Use
flaxon migrate for the application-owned schema before the first deploy.
The Admin exposes durable resumable media upload endpoints, Admin model CSV/JSON import/export endpoints, notification preferences, audit-chain verification, and provider-backed WebAuthn endpoints. See the Admin API Reference for paths and CSRF requirements.
Do not claim that WebAuthn or antivirus scanning is enabled merely because the Flaxon adapters are present. Inject a maintained WebAuthn provider and a real scanner service, then test their ceremonies and failure behavior in staging.
How Admin and CMS Work Together
Admin is the authenticated operations surface. It owns users, groups, permissions, settings, activity, notifications, operations, media, and registered application models. CMS is an authenticated editorial surface mounted on the same application. It owns content types, records, revisions, taxonomies, comments, menus, publishing status, and scheduled publishing.
The request flow is:
- The browser signs in at
/admin/loginand receives the configured session cookie. - Admin and CMS resolve the current user through the shared auth backend.
- The route checks the exact capability, such as
post.view_post,post.change_post, orcms.publish_content. - Browser mutations include the rendered CSRF token in the form or the
X-CSRF-Tokenheader. - The model or content adapter validates and writes the record, then records activity, audit data, revisions, and notifications where applicable.
- The response updates the Admin page or the CMS SPA; public applications consume their own public routes or APIs and never use Admin credentials.
Use Admin when a person manages the application or infrastructure. Use CMS when an editor creates and publishes site content. A user may have both roles, but the exact permission matrix remains the authority for every action.
Media Library Workflow
Open /admin/media to use the production media workspace. The page supports:
- Search across filename, original filename, title, and alt text.
- Filtering by folder and image/document/other type.
- Newest, oldest, name, and size sorting with server-side pagination.
- Grid and list layouts stored in the browser for the operator.
- Multi-select bulk deletion with CSRF protection.
- Upload dialog with drag-and-drop, folder selection, and upload progress.
- Image previews, dimensions, thumbnail status, file size, and content type.
- Metadata editing for alt text, title, caption, description, and credit.
- Rename with path sanitization and collision protection.
- Folder creation and folder deletion.
- Open, copy, and signed-object URL actions.
The browser UI is not the security boundary. The backend rechecks the user, CSRF token, filename, storage path, allowed content type, size, image dimensions, antivirus result, and object existence for every mutation. CMS image/file fields use the reusable media picker, which stores the selected asset URL or identifier instead of accepting an unchecked path.
For local development, use storage_path and upload_dir. For production,
inject an S3-compatible adapter with list, read, write, delete,
exists, size, get_url, and preferably get_signed_url. Configure an
antivirus scanner and a shared AdminStore before enabling multiple workers.
Capability Setup
New installations expose named groups in /admin/roles: Administrator,
Content Editor, Publisher, Media Manager, Warehouse Staff, Support Agent, and
Read Only. The screen presents descriptions and checkboxes; developers do not
need to make operators type permission keys. Applications can add capabilities
with admin.register_permission(...) and protect custom Jinax pages with
admin.add_view(..., permission="your.capability").
Model capabilities are generated when a model is registered. In strict mode,
the four standard capabilities are view, add, change, and delete in
the stable <model>.<action>_<model> form. Legacy keys remain readable during
migration, but new deployments should use strict permissions and least
privilege groups.
What 100% Means in Production
The bundled Admin/CMS workflows are functional when their required services are configured. “Complete” does not mean external services are silently invented: Redis must be supplied for shared sessions, limits, locks, and events; object storage must be supplied for S3 assets; a mail sender must be supplied for reset and verification delivery; a WebAuthn provider must be supplied for passkeys; and an antivirus scanner must be supplied for malware inspection. Without those integrations, Flaxon returns an explicit capability or configuration error rather than pretending the feature is active.
Use this capability in a full-stack app
Flaxon serves Python APIs and Teloce interfaces in one project. Keep validation, authorization and privileged operations on the server; components call those endpoints and present their results. Follow the full-stack lessons and Teloce integration reference to connect this capability to pages, components and application modules.