Skip to main content

djxi logo HTMX Integration for Django

PyPI PyPI - Python Version PyPI - Django Version CI codecov pre-commit

Full documentation →


HTMX features tend to scatter across urls.py, views.py, and a handful of template snippets. djxi solves this by bundling the URL patterns, view logic, and HTML sections for a feature into a single class — the DXEndpointBattery.

from djxi import DXEndpointBattery, dx_get, dx_post, dx_delete

class TodoBattery(DXEndpointBattery):
    inline_template = """
    <dx-section name="list">
        <ul>{% for item in items %}<li>{{ item }}</li>{% endfor %}</ul>
    </dx-section>
    <dx-section name="form">
        <form hx-post="{% url 'todo:create' %}">...</form>
    </dx-section>
    """

    @dx_get("list", name="list")
    def list(self, request):
        return self.render_section(request, "list", {"items": Item.objects.all()})

    @dx_post("create", name="create")
    def create(self, request):
        Item.objects.create(title=request.POST["title"])
        return self.render_section(request, "list", {"items": Item.objects.all()})

    @dx_delete("item/<int:pk>/delete", name="delete")
    def delete(self, request, pk):
        Item.objects.filter(pk=pk).delete()
        return self.render_empty(request)
# urls.py
urlpatterns = [
    path("todo/", include((TodoBattery.url_patterns(), "todo"), namespace="todo")),
]

Features

Feature Description
DXEndpointBattery Bundles URLs, view logic, and HTML in one class
<dx-section> / <dx-include> Split templates into named, reusable sections
Routing decorators @dx_get, @dx_post, @dx_put, @dx_patch, @dx_delete, @dx_action
battery_prefix Per-battery default URL prefix (overrides global DX_ROUTER_PREFIX)
Async handlers async def methods get ASGI-compatible views; arender_section() for async rendering
Permission hooks requires_auth, login_url, and check_permissions() override
HTMX headers request.htmx / response.htmx with fluent chainable setters
Method override X-HTTP-Method-Override header and <input name="_method"> POST field
Django messages Out-of-band message injection via hx-swap-oob
djxi_routes Management command listing all registered battery routes
djxi.testing DXRequestFactory, DXBatteryTestCase, assertion helpers
Typed Full type annotations; ships py.typed for PEP 561
HTMX 2 & 4 Both HTMX versions supported; switch with DX_HTMX_VERSION

Installation

pip install djxi

settings.py:

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

MIDDLEWARE = [
    # ... Django middleware ...
    "djxi.middleware.DjxiHeadersMiddleware",  # optional but recommended
]

Base template:

{% load djxi %}
<!doctype html>
<html>
  <head>
    {% htmx_script_inclusion %}
  </head>
  <body {% htmx_headers %}>
    {% flash_messages_inclusion %}
    {% block content %}{% endblock %}
  </body>
</html>

Core Concepts

Sections and Includes

Templates are split using custom HTML tags (no Django template syntax required — they sit alongside it):

<dx-section name="item-form">
  <form hx-post="{% url 'todo:create' %}">
    <input name="title">
    <button>Add</button>
  </form>
</dx-section>

<dx-section name="item-row">
  <li id="item-{{ item.pk }}">
    {{ item.title }}
    <dx-include name="item-actions"/>   {# reuse another section inline #}
  </li>
</dx-section>

<dx-section name="item-actions">
  <button hx-delete="{% url 'todo:delete' item.pk %}">Delete</button>
</dx-section>

Routing

Decorators mark methods as endpoints. url_patterns() converts them to Django paths:

class MyBattery(DXEndpointBattery):
    battery_prefix = "htmx"  # overrides DX_ROUTER_PREFIX for this battery

    @dx_action("item/<int:pk>", methods=["GET", "POST"], name="item")
    def item(self, request, pk):
        ...

    @dx_delete("item/<int:pk>/delete", name="item-delete")
    def delete(self, request, pk):
        ...
urlpatterns = [
    path("api/", include(MyBattery.url_patterns())),
    # → GET/POST  api/htmx/item/<pk>
    # → DELETE    api/htmx/item/<pk>/delete
]

HTMX Headers (Middleware)

@dx_put("item/<int:pk>/flag", name="flag")
def flag(self, request, pk):
    item = Item.objects.get(pk=pk)
    response = self.render_section(request, "item-row", {"item": item})
    # Fluent chaining — all setters return self
    response.htmx.set_trigger("itemFlagged").set_retarget(f"#item-{pk}")
    return response

Async Handlers

@dx_get("items/", name="items")
async def items(self, request):
    items = [item async for item in Item.objects.all()]
    return await self.arender_section(request, "item-list", {"items": items})

Permission / Auth Hooks

class ProtectedBattery(DXEndpointBattery):
    requires_auth = True      # 403 for anonymous users
    login_url = "/login/"     # redirect instead of 403

    # Or fine-grained:
    def check_permissions(self, request):
        if not request.user.has_perm("myapp.can_edit"):
            return HttpResponseForbidden()
        return None  # allow through

Testing

from djxi.testing import DXBatteryTestCase

class TodoBatteryTests(DXBatteryTestCase):
    def test_list_renders_items(self):
        request = self.dx.get("/")          # middleware already applied
        response = TodoBattery().render_section(request, "list", {"items": []})
        self.assert_section_rendered(response, "No todos found")

Configuration

All settings are optional. Override in your settings.py:

Setting Default Description
DX_HTMX_VERSION "4" HTMX version for CDN script tag ("2" or "4")
DX_HTMX_COMPRESSION ".js" Script variant (".js" or ".min.js")
DX_ROUTER_PREFIX "dx" Global URL prefix prepended to all battery routes
DX_MESSAGE_CONTAINER_ID "message-container" DOM ID for OOB message swap target
DX_MESSAGE_SWAP_METHOD "beforeend" hx-swap-oob insertion method
DX_MESSAGE_TEMPLATE "djxi/messages/message_list.html" Template for the message list
DJXI_ROUTE_MODULES ["views","endpoints","batteries"] Modules scanned by djxi_routes

Constants (cannot be overridden via settings.py):

Constant Value
DX_SECTION_TAG "dx-section"
DX_INCLUDE_TAG "dx-include"

Development Status

Pre-Alpha — experimental. API may change between minor versions.

  • v0.2.0: Public Alpha — improve existing facilities / coverage
  • v0.3.0: Public Beta
  • v1.0.0: Stable release

Download files

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

Source Distribution

djxi-0.1.9.tar.gz (45.8 kB view details)

Uploaded Source

Built Distribution

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

djxi-0.1.9-py3-none-any.whl (54.6 kB view details)

Uploaded Python 3

File details

Details for the file djxi-0.1.9.tar.gz.

File metadata

  • Download URL: djxi-0.1.9.tar.gz
  • Upload date:
  • Size: 45.8 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for djxi-0.1.9.tar.gz
Algorithm Hash digest
SHA256 d336655f6a357a89d1262ae28514a2f67f3e1d22af729f2292c646722b0de98c
MD5 f87536bfeff8c9ffa50ef8ec8e1ce0e0
BLAKE2b-256 65a6a9ba8a2443c4a4c1417c23aa7c171e9ee7a4ee18f111e02a5c4a99fa3fd9

See more details on using hashes here.

Provenance

The following attestation bundles were made for djxi-0.1.9.tar.gz:

Publisher: main.yml on rollinger/djxi

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file djxi-0.1.9-py3-none-any.whl.

File metadata

  • Download URL: djxi-0.1.9-py3-none-any.whl
  • Upload date:
  • Size: 54.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for djxi-0.1.9-py3-none-any.whl
Algorithm Hash digest
SHA256 dc4b42e164a74851758d88a7cff82abb312e2172d051677e44fe34e880db7f3d
MD5 d97d4893841dae0705483b1c31871256
BLAKE2b-256 fd1924fd9ecabff7b6877d5a12bfcd8989459258ab78eac6ff1fe646e950304e

See more details on using hashes here.

Provenance

The following attestation bundles were made for djxi-0.1.9-py3-none-any.whl:

Publisher: main.yml on rollinger/djxi

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

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