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.

Framework source · Markdown

Models, settings, and management commands

Flaxon 0.3.0 integrates Tortoise ORM 1.1.7. flaxon.db exports its models, fields, and transaction helpers. Existing flaxon.database adapters and JSON migration runners remain available for existing applications; new full-stack starters use Python migrations.

Start a project

Install the published release:

python -m pip install "flaxon[standard,admin]==0.3.0"
flaxon new project_manager
cd project_manager
python -m pip install -e .
python management.py check
python management.py makemigrations
python management.py migrate --plan
python management.py migrate
python management.py setup-admin
python management.py runserver
# Prepare the full-stack starter for deployment:
python management.py build

The CLI creates .venv; activate it before installing your generated project. flaxon run app:app --reload continues to work. runserver is a development command; use an ASGI server without reload for production.

File responsibilities

File Responsibility
settings.py Paths, database URL, security, enabled Admin/CMS features
app.py Create the application and explicitly mount modules
models.py Root project models
admin.py register(admin) explicitly registers editable models
management.py Small entry point to Flaxon's management command system
migrations/ Generated, version-controlled Python migration files

There is no module mount list in settings. The same mounts in app.py are used by the web server and management commands. The starter's management mode skips Jinax/Teloce setup and AdminStore construction; model discovery doesn't compile JavaScript or connect to the database.

Settings

from pathlib import Path
from flaxon.config import env

BASE_DIR = Path(__file__).resolve().parent
env.load(BASE_DIR / ".env")
PROJECT_NAME = "project_manager"
DEBUG = env.bool("FLAXON_DEBUG", default=True)
SECRET_KEY = env.str("FLAXON_SECRET_KEY")
DATABASE_URL = env.str("DATABASE_URL", default=f"sqlite://{BASE_DIR / 'data/app.sqlite3'}")
ALLOWED_HOSTS = env.list("FLAXON_ALLOWED_HOSTS", default=["localhost", "127.0.0.1"])
CSRF_TRUSTED_ORIGINS = env.list("FLAXON_CSRF_TRUSTED_ORIGINS", default=[])
TIME_ZONE = "UTC"
ADMIN_ENABLED = True
CMS_ENABLED = True
ADMIN_SERVICES_ENABLED = False
ADMIN_STORAGE_PATH = BASE_DIR / "data/admin.sqlite3"

Environment variables take precedence over .env. Booleans accept true/false, yes/no, on/off, and 1/0; other values fail clearly. Lists are comma-separated. Use absolute SQLite paths so commands run from any directory select the same DB. Production (DEBUG=False) requires a persistent secret of at least 32 characters and explicit hosts. Trusted CSRF origins include the scheme and host, without a path. UTC and timezone-aware ORM timestamps are the default.

For PostgreSQL, install flaxon[postgres] and set a Tortoise-compatible URL: postgres://USER:URL_ENCODED_PASSWORD@HOST:5432/DATABASE. The starter's staff and CMS metadata use SQLite by default. To put them in the same ORM database, set ADMIN_STORE_BACKEND = "orm" before generating and applying migrations. The existing PostgreSQLAdminStore remains available for manually configured apps. Changing the domain ORM URL does not automatically migrate Admin metadata.

Models and queries

from flaxon.db import Model, fields

class Project(Model):
    id = fields.IntField(primary_key=True)
    name = fields.CharField(max_length=120, unique=True)
    created_at = fields.DatetimeField(auto_now_add=True)

    def __str__(self):
        return self.name
project = await Project.create(name="Launch the website")
projects = await Project.filter(name__icontains="website").order_by("-created_at")
project.name = "Launch the portfolio"
await project.save()

Queries run in Flaxon's application-local ORM context during HTTP/WebSocket requests. For standalone async work, use async with app.db:. Do not use a process-global Tortoise fallback to share connections across applications.

Module-owned models

from flaxon.modules import FlaxonModule

projects = FlaxonModule(
    "projects",
    models_module="modules.projects.models",
    admin_module="modules.projects.admin",
    migrations_module="modules.projects.migrations",
)

Mount explicitly in app.py after Flaxon.from_settings():

app.mount_module(projects, prefix="/api/projects")

The migrations path defaults to the model package's migrations package. admin_module defaults to a sibling admin.py if present. Module names are ORM app labels and must be unique. Root models use the models label. Relations use these labels, e.g. fields.ForeignKeyField("models.Project").

Admin registration

from models import Project

def register(admin):
    admin.register(Project, search_fields=["name"], ordering=["-created_at"])

Call configure_admin(app) after mounting modules in the non-management branch of your factory. It creates a project-local registry, strict permissions, durable staff accounts, optional CMS, and session-bound CSRF. Only registered models are exposed. Primary keys and automatic timestamps are read-only. Forms derive basic types, required flags and length limits from ORM fields. Foreign keys use a permission-filtered choice list up to 100 related records, otherwise a searchable paginated selector. Many-to-many fields receive multiple-selection controls. Related models must be explicitly registered; both displayed choices and submitted IDs obey their model/object permissions. Model validation remains authoritative.

Management commands

  • check: validate settings, discover models, and validate Admin registrations.
  • makemigrations [LABEL] --name DESCRIPTION: generate Python migrations.
  • makemigrations LABEL --empty: scaffold a data/custom migration.
  • migrate [LABEL [MIGRATION]]: apply migrations or target a prior migration.
  • migrate --status: show applied migration history.
  • migrate --plan: show pending operations without applying them.
  • sqlmigrate LABEL MIGRATION: inspect the generated SQL.
  • setup-admin / createsuperuser: prompt for a staff account and hidden password.
  • shell: Tortoise's ORM-aware shell; install tortoise-orm[ipython] first.
  • runserver: development server; accepts --host, --port, --no-reload.
  • Module commands remain available through flaxon_cli.py, e.g. welcome.

Commit migration files. Review generated operations before deployment. Server startup opens connections, not tables; it never runs migrations automatically. Application-auth accounts and staff Admin accounts are separate concerns.

Existing JSON migrations

Do not delete JSON history or run a new initial migration against populated legacy tables. Back up the database, keep the legacy runner operational, and compare model-generated schema with the existing schema in a disposable clone. A matching existing schema can be baselined with Tortoise's migrate --fake after review; use the Tortoise CLI with an explicitly exported ORM configuration for that advanced operation. The default Flaxon wrapper does not offer fake migration flags. Test subsequent migration/rollback and restore before switching production. There is no automatic data conversion or assumption that the old migrations tracking table means the new ORM migration has been applied.

Admin hardening and compatibility

The starter uses strict_permissions=True and session_bound_csrf=True. Existing manual AdminDashboard setups retain legacy defaults for compatibility; opt into those settings and use a shared persistent SECRET_KEY across workers. Always fetch a fresh CSRF token after sign-in; pre-login tokens are not valid for the authenticated session. Configured browser Origins are enforced on mutations. CSRF protection does not authorize a user: model and object rules are enforced separately on search, export, edit, delete, and bulk actions.

CMS editing requires authentication by default. Explicit allow_unauthenticated is only for deliberately public/demonstration setups. A standalone authenticated CMS without AdminDashboard must supply its own CSRF protector. Publication rights are checked for edits to live content, imports, scheduling, and restoration.

ORM lists use database pagination. Legacy custom get_instances() adapters and Python object-permission hooks may still scan records in memory; large or multi-tenant datasets need scoped database adapters. Audit/export/search snapshots redact credential-like keys. This is targeted hardening, not a complete security certification or parity claim with Django Admin.

Simple Admin customization

from models import Project, Task

def register(admin):
    admin.register(
        Project,
        search_fields=["name"],
        fieldsets={"Details": ["name", "description"]},
        queryset=lambda user, query: query.filter(owner_id=user.id),
        inlines={"tasks": {"model": Task, "fk": "project_id"}},
    )
    admin.register(Task, search_fields=["title"])

queryset(user, query) scopes the base ORM query for lists, lookups, relationship choices, search and export. Return a QuerySet; an async function may return one. Use it for tenant/ownership filtering. Optional can_view, can_add, can_change, and can_delete hooks provide additional business rules. Python object hooks filter before pagination, giving correct counts, but may scan records; the query hook avoids that cost for large tables.

fieldsets inserts named headings before the first configured field. Existing fields still controls order. widgets={"body": "admin/widgets/body.html"} selects a trusted Jinax template for a field; templates receive field_name, schema, and current_value. Never derive template names from request input.

Inlines edit registered child models within a parent form. fk names the child's foreign-key ID column. Each operation enforces the child's create/change/delete permission and hook, and existing children must belong to that parent. Parent and child changes share a transaction. Forms support at most 100 children and 200 many-to-many selections; larger workflows should use the separate model list. Inline fields currently use scalar inputs; complex child editors can use a custom Admin view. Custom business side effects must run after a successful transaction; database rollback cannot undo email or external service calls.

Validation errors preserve submitted scalar values and display field errors. Unique/relationship conflicts remain explicit 409 responses. ORM edit forms include a fingerprint of the record; stale or missing versions cannot overwrite newer changes. Atomic conditional writes protect against concurrent saves, including models without updated_at. The fingerprint includes many-to-many selections. Inline existing-child saves carry child versions.

Deletion confirmation shows direct dependent counts and database deletion rules. Cascade deletion additionally checks registered dependent-model and object delete permissions recursively. Protected relationships produce a readable conflict.

ORM imports are atomic by default. ?allow_partial=true gives each row its own transaction and reports the actual successful count; external side effects remain the application's responsibility. Import preview runs the configured import validator; database constraints are checked when applying the import.

ORM-backed Admin/CMS metadata

Opt in through settings.py:

ADMIN_STORE_BACKEND = "orm"
python management.py makemigrations
python management.py migrate
python management.py setup-admin

Flaxon adds the flaxon_admin model label and generated admin_migrations/ package. Commit those Python migrations. This stores staff records, sessions, CMS metadata, audit metadata and operations through the ORM in DATABASE_URL. No Admin table is created silently on server startup. The store preserves the existing synchronous service contract using a dedicated thread/event loop with separate ORM connections; calls are synchronous and are not a nonblocking request API. Media file bytes still need persistent filesystem or object storage.

To move an existing SQLite store, stop all writers, back up both databases, apply the ORM migrations, and copy into an empty destination:

python management.py migrate-admin-store /absolute/path/to/admin.sqlite3

The copy is transactional, preserves operation timestamps, refuses a nonempty destination, and leaves the source unchanged. Test sign-in, CMS records, sessions and audit history before switching production traffic. Retain the source backup for rollback. Changing the setting by itself does not copy existing records.

Admin bundles Alpine.js 3.14.3 with its MIT license so the CMS and core Admin interactions do not require downloading that runtime from a CDN. Cosmetic fonts, icons and Tailwind enhancements still reference external assets. Browser regression tests block those external requests and exercise the local controls.

Admin/CMS and database adapters

The ORM lifecycle object at app.db is not the older SQL adapter interface. Admin and CMS use their durable AdminStore by default; they only automatically use a legacy database object implementing both execute() and fetch_all(). Choose ADMIN_STORE_BACKEND = "orm" explicitly to store metadata through the ORM. Do not pass the lifecycle object as database= to AdminDashboard or CMS.

Learn the ORM by building an application

The project manager models chapter supplies complete models and exact migration commands, then explains flaxon.db.Model, fields, async ownership-scoped queries and in_transaction. Continue through authentication, projects, tasks and staff registration. The course contents uses the published Flaxon 0.3.0 installation.