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.1.tar.gz (30.6 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.1-py3-none-any.whl (32.7 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: djact-4.1.1.tar.gz
  • Upload date:
  • Size: 30.6 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.1.tar.gz
Algorithm Hash digest
SHA256 a501e29fbf0840971e34b50fb83cef5741b96d4960be675eef5ef3b0a5216dc3
MD5 5dfaac69b2aec542d98c6de11576d505
BLAKE2b-256 2fc7efd9d30ac84f24098b8a906e8c6500a75995ee5b7483bc2b28e75de7bb53

See more details on using hashes here.

File details

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

File metadata

  • Download URL: djact-4.1.1-py3-none-any.whl
  • Upload date:
  • Size: 32.7 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.1-py3-none-any.whl
Algorithm Hash digest
SHA256 7c90a0af80226fa3df44f0460b2ccaae7b8d3c261fe99954b43969098f4eb910
MD5 ada2078367d7b0d25cf185279688cf47
BLAKE2b-256 f4d493c424b63af6cd3ea230524bf3a3bf975270aa5bc18292f65b10a0ab4d92

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