Eneza CLI Framework — Developer Guide

Table of Contents

  1. Overview
  2. Installation
  3. Quick Start
  4. Command Reference
  5. Project Structure
  6. App Structure
  7. Menu Behavior
  8. URL Configuration
  9. Deployment
  10. Best Practices
  11. Troubleshooting
  12. Extending the Templates
  13. Validation

Overview

The Eneza CLI creates Eneza-based Django projects and apps with the current framework conventions. It also adds production Docker/Coolify scaffolding to new or existing projects.

Features:

  • eneza startproject creates a dashboard-first project with the current framework settings.
  • eneza startapp creates a models/views scaffold with no sample model or sample view.
  • eneza adddeployment adds Docker/Coolify files to an existing project.
  • Projects reference framework apps through one import: ENEZA_APPS.
  • Projects include an ASGI entry point, websocket routing, Docker Compose, init/worker/periodic scripts, and an environment sample.
  • The CLI supports the current Eneza app conventions: models/ and views/ packages, dynamic URL registration, and dashboard-first menu entries.

Installation

Prerequisites

  • Python 3.12 or newer
  • Django 6.1 or newer
  • PostgreSQL 16 for Docker/Coolify deployments
  • Optional: Redis 7 for multiple web workers or cross-container websockets

Create a virtual environment

bash
python -m venv eneza-env

# Windows
eneza-env\Scripts\activate

# macOS/Linux
source eneza-env/bin/activate

Install the framework and CLI

bash
python -m pip install enezaframework

For development from a checkout:

bash
python -m pip install -e .

The eneza console script is installed with the package. python -m eneza also works.

If the eneza command is not found, the editable install is stale. Regenerate the console script:

bash
python -m pip install -e .

Without the console script, run the module directly:

bash
python -m eneza startproject myproject

Do not use python eneza; eneza is a package and requires the -m flag or the installed console script.

Generated projects pin enezaframework in requirements.txt. If the package is served from a private index, set PIP_INDEX_URL and credentials in CI or Coolify.


Quick Start

Local development with SQLite

bash
eneza startproject myproject
cd myproject

python -m venv .venv
.venv\Scripts\activate        # Windows
# source .venv/bin/activate   # macOS/Linux

python -m pip install -r requirements.txt
python manage.py migrate
python manage.py createsuperuser
python manage.py rbac_bootstrap
python manage.py seed_dashboards
python manage.py runserver

No .env file is required for local development. The settings default to SQLite, DEBUG=True, ALLOWED_HOSTS=["*"], and the development secret key.

If you create a .env for local development, set:

dotenv
DB_ENGINE=sqlite
DATABASE_URL=
DJANGO_DEBUG=1
DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1
DJANGO_SECURE_SSL_REDIRECT=0
DJANGO_SESSION_COOKIE_SECURE=0
DJANGO_CSRF_COOKIE_SECURE=0

Docker and Coolify

bash
eneza startproject myproject
cd myproject
cp .env.sample .env
# edit .env: set DJANGO_SECRET_KEY, POSTGRES_PASSWORD, hosts and superuser
docker compose up --build

The Compose file does not publish a host port because Coolify routes to container port 8000. For local browser access, add a ports mapping to the web service:

yaml
ports:
  - "${WEB_PORT:-8000}:8000"

Add an app

bash
eneza startapp myapp --project-dir .

Then add myapp to INSTALLED_APPS and include myapp.urls in the project URL configuration when you add views.


Command Reference

eneza startproject <project_name>

Creates a new Eneza project.

Arguments:

  • <project_name> - Name of the project to create.

Generated structure:

text
project_name/
├── Dockerfile
├── docker-compose.yml
├── .dockerignore
├── .env.sample
├── requirements.txt
├── manage.py
├── docker/
│   ├── init.sh
│   ├── web.sh
│   ├── worker.sh
│   └── periodic.sh
├── project_name/
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   ├── asgi.py
│   ├── wsgi.py
│   ├── routing.py
│   ├── config.py
│   └── workflowconfig/
└── core/
    ├── __init__.py
    ├── admin.py
    ├── apps.py
    ├── models/
    │   └── __init__.py
    ├── views/
    │   └── __init__.py
    ├── urls.py
    ├── tests.py
    └── migrations/
        └── __init__.py

The project root URL resolves to the Enezacommon dashboard flow. core is included last at / and contains no sample root view.

eneza startapp <app_name>

Creates an app with the standard Eneza structure.

Arguments:

  • <app_name> - Name of the app to create.

Options:

  • --project-dir - Project directory (default: current directory).

Generated structure:

text
app_name/
├── __init__.py
├── admin.py
├── apps.py
├── models/
│   └── __init__.py
├── views/
│   └── __init__.py
├── urls.py
├── tests.py
└── migrations/
    └── __init__.py

The app scaffold contains no sample model and no sample view. Both models/ and views/ directories exist with their __init__.py files.

eneza adddeployment [project_dir]

Adds Docker/Coolify deployment artifacts to an existing project.

Arguments:

  • [project_dir] - Existing project directory (default: current directory).

Options:

  • --project-name - Django project package name. When omitted, the CLI reads DJANGO_SETTINGS_MODULE from manage.py, then falls back to the directory name.
  • --force - Overwrite existing deployment artifacts.

Copied artifacts:

text
Dockerfile
.dockerignore
docker-compose.yml
.env.sample
requirements.txt   (only when absent, or with --force)
docker/init.sh
docker/web.sh
docker/worker.sh
docker/periodic.sh

The command:

  • Validates that manage.py exists.
  • Replaces PROJECT_NAME_PLACEHOLDER and PROJECT_DB_NAME_PLACEHOLDER.
  • Skips existing files by default.
  • Never creates or overwrites .env.
  • Never patches settings.py or urls.py.

After running it, review the printed checklist:

  • Add from enezaframework.conf import ENEZA_APPS and *ENEZA_APPS to INSTALLED_APPS.
  • Include enezaframework.urls in the project URL configuration.
  • Merge the deployment requirements into the existing requirements file.
  • Create .env from .env.sample.
  • Run the init commands, or start the Compose stack.

Project Structure

Framework app reference

Projects import the framework app list once:

python
from enezaframework.conf import ENEZA_APPS, ENEZA_CONTEXT_PROCESSORS

INSTALLED_APPS = [
    # Django and third-party apps...
    *ENEZA_APPS,
    # Project apps...
    "core",
]

TEMPLATES = [
    {
        "OPTIONS": {
            "context_processors": [
                # Django and third-party processors...
                *ENEZA_CONTEXT_PROCESSORS,
            ],
        },
    },
]

ENEZA_APPS currently contains:

text
enezaframework, enezacommon, enezarbac, enezareporting, enezadashboard,
enezacommunication, enezaadminstrator, enezaworkflowengine,
enezanotification, enezaagents, humanresource

Adding or removing a framework app is a one-line change in enezaframework/conf.py. Projects do not list the framework apps individually.

URL configuration

The generated urls.py includes the framework once:

python
from django.contrib import admin
from django.urls import include, path


urlpatterns = [
    path("admin/", admin.site.urls),
    path("accounts/", include("allauth.urls")),
    path("", include("enezaframework.urls")),
    path("", include("core.urls")),
]

enezaframework.urls includes the framework modules, the dashboard entry, media serving when enabled, and the existing path prefixes:

  • page-views/
  • administration/
  • administration/rbac/
  • workflow/
  • communication/
  • reporting/
  • dashboards/
  • agents/
  • humanresource/

Optional apps are included only when installed.

Settings

The generated settings are environment-driven:

  • DJANGO_SECRET_KEY is required when DJANGO_DEBUG=0.
  • DJANGO_ALLOWED_HOSTS falls back to COOLIFY_FQDN.
  • DJANGO_CSRF_TRUSTED_ORIGINS falls back to COOLIFY_URL.
  • DB_ENGINE=postgres uses DATABASE_URL; SQLite is the default for local development.
  • DJANGO_STATIC_ROOT and DJANGO_MEDIA_ROOT control the shared volumes.
  • REDIS_URL enables the Redis channel layer and cache.
  • TASKS declares the default and workflow_tasks queues.
  • SERVE_MEDIA enables the media route when DEBUG=0.

Security settings are environment-driven: DJANGO_SECURE_SSL_REDIRECT, DJANGO_SESSION_COOKIE_SECURE, DJANGO_CSRF_COOKIE_SECURE, DJANGO_SECURE_HSTS_SECONDS, DJANGO_SECURE_PROXY_SSL_HEADER, DJANGO_X_FRAME_OPTIONS, and DJANGO_SECURE_CONTENT_TYPE_NOSNIFF.


App Structure

text
myapp/
├── __init__.py
├── admin.py
├── apps.py
├── models/
│   └── __init__.py
├── views/
│   └── __init__.py
├── urls.py
├── tests.py
└── migrations/
    └── __init__.py

apps.py

A generated app starts with an app config that is menu-eligible but has no sample menu items:

python
from django.apps import AppConfig


class myappConfig(AppConfig):
    default_auto_field = "django.db.models.BigAutoField"
    name = "myapp"
    menu_verbose_name = "Myapp"
    app_entry_menu = "myapp:myapp_dashboard"
    app_icon = "app-placeholder-icon"
    menu_position = 10
    menus = []

app_entry_menu marks the app as menu-eligible. The module menu resolves the real entry URL to enezadashboard:module_entry for the app label. The dashboard app then shows its empty state until a dashboard is designed for the module.

The menus list stays empty until real views exist. Add fast tabs and menu items when you add views.

urls.py

The generated URL module contains only dynamic registration:

python
from enezaframework.utils import get_registered_urls


app_name = "myapp"

dynamic_urls = get_registered_urls(app_name)

urlpatterns = [
    *dynamic_urls,
]

Views decorated with @auto_url_register are picked up automatically. No sample dashboard route is generated.

models/ and views/

Both packages are created empty with __init__.py. Add one file per model or view group and re-export the symbols from the package __init__.py:

python
# myapp/models/__init__.py
from .customertable import CustomerTable
python
# myapp/views/__init__.py
from .customerviews import *

The first menu destination for a new app is the dashboard app:

text
/dashboards/<app_label>/

If no dashboard is defined for the module, Enezadashboard renders its empty state instead of a sample dashboard view. This is the intended behavior for a fresh CLI scaffold.

The app appears in the module menu because it defines menu_verbose_name, app_icon, and app_entry_menu. The fast tabs remain empty until menus is populated.

If a project removes enezadashboard from ENEZA_APPS, the scaffold must define a real app_entry_menu URL or omit the menu attributes.


URL Configuration

Dynamic URL registration

Each app uses get_registered_urls():

python
from enezaframework.utils import get_registered_urls

app_name = "myapp"
dynamic_urls = get_registered_urls(app_name)

urlpatterns = [
    *dynamic_urls,
]

Auto URL registration

Views can be registered automatically:

python
from enezaframework.utils import auto_url_register
from enezaframework.views import BaseListView, BaseDetailView


@auto_url_register
class MyAppListView(BaseListView):
    model = MyAppModel

    def set_list_fields(self):
        return ["name", "description", "created_at"]


@auto_url_register
class MyAppDetailView(BaseDetailView):
    model = MyAppModel

    def detail_ui_view(self):
        return [
            {
                "name": "Basic Information",
                "icon": "Home",
                "Fieldcolumns": [
                    {"fields": ["name", "description"]},
                ],
            },
        ]

Generated URL names include:

  • myapp:myapp_list
  • myapp:myapp_create
  • myapp:myapp_detail
  • myapp:myapp_update
  • myapp:myapp_delete

Add the app's URL module to the project URL configuration before using these names.

Supported view types

The registry supports list, detail, create, delete, action, parameter, dashboard, autocomplete, workflow and document report views.


Deployment

Compose services

Service Purpose
db PostgreSQL 16 with a pg_isready healthcheck.
init One-shot migrations, cache table, site update, superuser bootstrap, RBAC bootstrap, dashboard seeding and static collection.
web Uvicorn serving <project>.asgi:application on port 8000.
worker python manage.py db_worker --queue-name=default,workflow_tasks.
periodic python manage.py run_periodic.
redis Optional Redis 7 service under the redis Compose profile.

web, worker and periodic wait for init to complete successfully.

Init sequence

bash
python manage.py migrate --noinput
python manage.py createcachetable
python manage.py ensure_site
python manage.py ensure_superuser
python manage.py rbac_bootstrap
python manage.py seed_dashboards
python manage.py collectstatic --noinput
  • ensure_superuser creates the bootstrap admin from DJANGO_SUPERUSER_* and never resets an existing password.
  • ensure_site updates the django.contrib.sites row from DJANGO_SITE_DOMAIN or COOLIFY_FQDN.
  • rbac_bootstrap seeds the security roles and permissions.
  • seed_dashboards loads the declarative dashboards for installed framework apps.

Set SEED_HR_ROLES=1 to also run python manage.py seed_hr_roles.

Set SEED_HR_SAMPLE=1 to create a small sample HR organization and a linked demo user. The init script runs seed_hr_roles and then:

bash
python manage.py seed_hr_sample --role HR_MANAGER

The sample command creates or reuses the Sample Company entity, a Human Resources department, job and position, an employee, and a user linked to that employee. It grants the entity to the user and assigns the requested roles. It is idempotent and disabled by default.

Useful options:

  • --no-user - create HR data only.
  • --no-entity-access - do not grant the sample entity to the user.
  • --reset-password - reset an existing sample user's password.
  • --dry-run and --json.

A newly created sample user gets the login sample.hr@example.com and password Sample@123. Existing users keep their current password unless --reset-password is used. Change the demo password before sharing. Sample data is for demos; do not enable SEED_HR_SAMPLE in production.

Required environment

Copy .env.sample to .env and set at least:

dotenv
DJANGO_SECRET_KEY=<long-random-value>
POSTGRES_PASSWORD=<url-safe-password>
DJANGO_ALLOWED_HOSTS=
DJANGO_SUPERUSER_USERNAME=admin
DJANGO_SUPERUSER_EMAIL=admin@example.com
DJANGO_SUPERUSER_PASSWORD=<strong-password>

Use a URL-safe POSTGRES_PASSWORD (no @, :, or /) because it is embedded in DATABASE_URL.

Leave DJANGO_ALLOWED_HOSTS and DJANGO_CSRF_TRUSTED_ORIGINS blank on Coolify to use COOLIFY_FQDN and COOLIFY_URL.

Coolify

  1. Create a Docker Compose resource from the repository.
  2. Set the environment variables from .env.sample.
  3. Configure the domain on the web service at container port 8000.
  4. Keep the pgdata, media_data and static_data volumes persistent.
  5. Set SERVE_MEDIA=1 unless media is served by object storage or a reverse proxy.
  6. Keep UVICORN_WORKERS=1 unless REDIS_URL is configured.

For local HTTP Docker testing, set:

dotenv
DJANGO_SECURE_SSL_REDIRECT=0
DJANGO_SESSION_COOKIE_SECURE=0
DJANGO_CSRF_COOKIE_SECURE=0
DJANGO_ALLOWED_HOSTS=localhost,127.0.0.1

Async work

Background jobs require the database task worker:

bash
python manage.py db_worker --queue-name=default,workflow_tasks

Scheduled workflow work requires the periodic runner:

bash
python manage.py run_periodic

The Compose stack starts both. Scheduling alone does not execute work.

Media and static files

  • init collects static files into the shared static_data volume; WhiteNoise serves them in production.
  • SERVE_MEDIA=1 serves /media/ through Django from the shared media_data volume.
  • For larger deployments, move media to S3-compatible storage or an nginx sidecar and set SERVE_MEDIA=0.

Optional Redis

In-memory channels work only with a single web worker. To scale workers or run websockets across containers:

  1. Enable the redis Compose profile, or add a Redis service.
  2. Set REDIS_URL=redis://redis:6379/0.
  3. Raise UVICORN_WORKERS above 1.

Best Practices

Naming conventions

  • Use snake_case for app names.
  • Use PascalCase for model and view class names.
  • Keep app_name in urls.py equal to the Django app label.

App organization

  • Keep models/ and views/ as packages with __init__.py.
  • Re-export models and views from the package __init__.py as you add files.
  • Keep admin.py, apps.py, tests.py at the app root.
  • Add migrations with python manage.py makemigrations <app>.
  • Leave menus=[] until real views exist.
  • Set app_entry_menu to a real URL when the app no longer uses the dashboard entry.
  • Use load_icon for icons and confirm the SVG exists under static/icons/.

URLs

  • Use @auto_url_register for framework CRUD views.
  • Include the app's URL module in the project URL configuration.
  • Use the generated URL names in menus only after the routes exist.

Troubleshooting

eneza command is not recognized

Reinstall the editable package so pip regenerates the console script:

bash
python -m pip install -e .

Then verify:

bash
eneza --help

If the script still is not available, use python -m eneza .... Do not run python eneza ...; Python treats the package directory as a script.

App does not appear in the module menu

Verify that the app is in INSTALLED_APPS and its AppConfig defines menu_verbose_name, app_icon, and app_entry_menu.

App menu entry opens the dashboard empty state

This is expected for a fresh scaffold. Design a dashboard for the app, or point app_entry_menu at a real view after adding one.

URLs do not resolve

Confirm the app's urls.py spreads *dynamic_urls and that the app URL module is included in the project URL configuration. Verify views are decorated with @auto_url_register.

NoReverseMatch for app_entry_menu

The default scaffold expects enezadashboard to be installed. Keep it in ENEZA_APPS, or define a real app_entry_menu URL.

Sample HR user cannot see HR menus

Run python manage.py seed_hr_roles, then rerun python manage.py seed_hr_sample --role HR_MANAGER. The sample command warns and skips roles that do not exist or are inactive.

Docker build cannot find Dockerfile

Use the standard filename Dockerfile. On Windows, a case-only Git rename from DockerFile may need:

bash
git mv DockerFile Dockerfile.tmp
git mv Dockerfile.tmp Dockerfile

Init fails because the secret key is a placeholder

Set a real DJANGO_SECRET_KEY when DJANGO_DEBUG=0.

Host validation fails on Coolify

Leave DJANGO_ALLOWED_HOSTS blank so settings use COOLIFY_FQDN, or set the real domain explicitly.

Websockets fail with multiple workers

Set REDIS_URL and enable Redis, or keep UVICORN_WORKERS=1.


Extending the Templates

  1. Edit the project templates under eneza/templates/project/.
  2. Edit the app templates under eneza/templates/app/.
  3. Update placeholder replacement and deployment assets in eneza/cli.py.
  4. Ensure new dotfiles are listed explicitly in setup.py package_data and MANIFEST.in.
  5. Rebuild and reinstall the package, then verify the generated output.

The deployment assets are shared by startproject and adddeployment; keep both paths working when adding a new artifact.


Validation

Per project rules, the developer or agent does not run or claim test results. The user is solely responsible for all testing and validation of generated projects, Docker images, migrations, seeding and Coolify deployments.