Skip to main content

HTML-first frontend interaction for Django, JSON-only server, and client-side DOM updates.

Project description

Djact v4.1

File-based reactive components for Django. No React, no Inertia — just Python classes and HTML directives.

Django renders the page normally. After that, all interactions happen via AJAX. Server returns JSON only. Client updates the DOM.


Features

  • File-based componentsdj:component="home" auto-loads components/home.py
  • Standard Django flowurls.py → views.py → render(template.html) stays unchanged
  • Python Component class — Clean, testable, standard Python
  • Reactive directivesdj:click, dj:submit, dj:model, dj:function, dj:state
  • Template expressions[[ count ]], [[ user.name ]] (no Django {{ }} conflict)
  • Auto-discovery — Components found automatically from installed apps
  • Auto Pagination UI — Laravel-style pagination with dark/light theme
  • SPA Navigationdj:navigate for page transitions without reload
  • Debug Panel — Next.js-style floating devtools (only in DEBUG mode)
  • Zero JS to write — Everything is declarative

Installation

pip install djact

1. Add to INSTALLED_APPS

INSTALLED_APPS = [
    # ...
    "djact",
]

2. Include URLs

from django.urls import path, include

urlpatterns = [
    path("", include("djact.urls")),
]

3. Enable Middleware

MIDDLEWARE = [
    # ...
    "djact.middleware.DjactAutoLoadMiddleware",
]

Quick Start

Step 1: Create a Component

myapp/
  components/
    __init__.py
    counter.py        ← dj:component="counter"
# myapp/components/counter.py

class Component:
    def mount(self, request):
        """Called on first page load. Return initial state."""
        return {"count": 0}

    def increment(self, request, data):
        """Called via dj:click. Return state updates."""
        return {"count": data["count"] + 1}

    def decrement(self, request, data):
        return {"count": data["count"] - 1}

Step 2: Create Template

# myapp/views.py
from django.shortcuts import render

def home(request):
    return render(request, "home.html")
<!-- myapp/templates/home.html -->
<!DOCTYPE html>
<html>
<head><title>Counter</title></head>
<body>

<div dj:component="counter" dj:state="count=0">
    <h1>Count: [[ count ]]</h1>
    <button dj:click="increment">+1</button>
    <button dj:click="decrement">-1</button>
</div>

</body>
</html>

That's it. No JavaScript needed.


Directives

Directive Type Description
dj:component="name" Setup Links HTML to a Python component
dj:state="key=val, ..." Setup Initial client-side state
dj:click="method" Event Calls server method on click
dj:click="method(arg)" Event Calls with arguments
dj:submit="method" Event Calls server method on form submit
dj:function="setState(...)" Event Client-side state update (no server call)
dj:model="field" Binding Two-way data binding for inputs
dj:for="item in list" Loop Repeat element for each item
dj:if="condition" Condition Show/hide based on expression
dj:empty="list" Condition Show only if list is empty
dj:paginate="list" Pagination Auto pagination controls

Component File Structure

Components are auto-discovered from installed apps:

myapp/
  components/
    __init__.py
    home.py               ← dj:component="home"
    counter.py            ← dj:component="counter"
    admin/
      __init__.py
      dashboard.py        ← dj:component="admin/dashboard"
      users.py            ← dj:component="admin/users"

Component Class

class Component:
    def mount(self, request):
        """Required. Returns initial state dict."""
        return {"items": [], "name": ""}

    def any_method(self, request, data):
        """Called via dj:click or dj:submit. Returns state updates."""
        return {"items": data["items"] + [data["name"]]}

    def delete(self, request, data, item_id):
        """Supports extra arguments: dj:click="delete(item.id)" """
        items = [i for i in data["items"] if i["id"] != item_id]
        return {"items": items}

CRUD Example

# myapp/components/users.py
from django.contrib.auth.models import User

class Component:
    def mount(self, request):
        users = list(User.objects.values("id", "username", "email")[:50])
        return {"users": users, "username": "", "email": "", "editing_id": None, "error": ""}

    def save_user(self, request, data):
        username = data.get("username", "")
        if not username:
            return {"error": "Username is required!"}
        User.objects.create_user(username=username, email=data.get("email", ""))
        users = list(User.objects.values("id", "username", "email")[:50])
        return {"users": users, "username": "", "email": "", "error": ""}

    def delete_user(self, request, data, user_id):
        User.objects.filter(id=user_id).delete()
        users = list(User.objects.values("id", "username", "email")[:50])
        return {"users": users}
<div dj:component="users" dj:state="users=[], username='', email='', editing_id=null, error=''">

    <div dj:if="error" style="color:red">[[ error ]]</div>

    <form dj:submit="save_user">
        <input dj:model="username" placeholder="Username">
        <input dj:model="email" placeholder="Email">
        <button type="submit">Save</button>
    </form>

    <table>
        <tr dj:for="user in users">
            <td>[[ user.username ]]</td>
            <td>[[ user.email ]]</td>
            <td><button dj:click="delete_user(user.id)">Delete</button></td>
        </tr>
    </table>

    <p dj:empty="users">No users yet.</p>
</div>

Settings

Setting Default Description
DJACT_ENDPOINT_URL Auto-resolved Override the AJAX endpoint URL
DJACT_COMPONENTS_MODULE None Explicit module path (e.g. "myapp.components")
NAVIGATE_COLOR #3b82f6 Link color for dj:navigate links
DEBUG False Enables the debug panel when True

Auto Pagination

Add dj:paginate to any container. It auto-generates a Laravel-style pagination UI.

# myapp/components/users.py
class Component:
    def mount(self, request):
        return {
            "users": User.objects.values("id", "name")[:10],
            "pagination": {
                "current_page": 1,
                "total_pages": 5,
                "has_next": True,
                "has_prev": False
            }
        }

    def change_page(self, request, data):
        page = data.get("__page", 1)
        per_page = 10
        offset = (page - 1) * per_page
        return {
            "users": User.objects.values("id", "name")[offset:offset+per_page],
            "pagination": {
                "current_page": page,
                "total_pages": 5,
                "has_next": page < 5,
                "has_prev": page > 1
            }
        }
<div dj:component="users">
    <div dj:for="user in users">[[ user.name ]]</div>
    <div dj:paginate="users"></div>
</div>

Theme override:

<div dj:paginate="users" dj:paginate.mode="dark"></div>
<div dj:paginate="users" dj:paginate.mode="light"></div>

SPA Navigation

Navigate between pages without full reload:

<a dj:navigate="/dashboard">Dashboard</a>
<a dj:navigate="/settings">Settings</a>

Shows a progress bar during navigation. Automatically re-initializes djact on the new page.


Debug Panel

Automatically appears when Django DEBUG = True. Floating button in bottom-right corner.

Shows:

  • Requests — Component name, method, latency, status, full JSON payload/response
  • Errors — Django server errors, JS errors, network failures
  • Logs — Timeline of all actions

How It Works

  1. Django renders HTML normally (standard urls.py → views.py → template)
  2. Middleware detects dj:component in the response and injects CSRF + JS assets
  3. Client JS (auto.js) boots: parses dj:state, calls mount() on server
  4. Server loads Component class via importlib, calls mount(), returns JSON
  5. Client updates DOM using [[ expression ]] interpolation
  6. User interactions (dj:click, dj:submit) send AJAX POST → server method → JSON → DOM update

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

djact-4.1.0.tar.gz (29.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

djact-4.1.0-py3-none-any.whl (31.2 kB view details)

Uploaded Python 3

File details

Details for the file djact-4.1.0.tar.gz.

File metadata

  • Download URL: djact-4.1.0.tar.gz
  • Upload date:
  • Size: 29.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.7

File hashes

Hashes for djact-4.1.0.tar.gz
Algorithm Hash digest
SHA256 1cdff079db3af57b5ad4d0afa98f9294fce99bccec68e5f5fd8cfd7a89d4a583
MD5 25d046d068feb25fe3e4384e5860d60d
BLAKE2b-256 31e6a1bd22382ccd4ac1cfc7169349dd26829685c8ff8d91b9ed191570b156e1

See more details on using hashes here.

File details

Details for the file djact-4.1.0-py3-none-any.whl.

File metadata

  • Download URL: djact-4.1.0-py3-none-any.whl
  • Upload date:
  • Size: 31.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.7

File hashes

Hashes for djact-4.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 9dc469e35b491331eff5d23368c75f6daebaafdf2b184e03ff433684e2994168
MD5 f90f3b117bf41db8ffb1001b32e20bbf
BLAKE2b-256 04fbadb8832b3125adc73b2bd15e7c674556ad0b75463689f02e2f8b97071e82

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page