Skip to content

Repository files navigation

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

About

HTMX Integration for Django

Topics

Resources

Stars

25 stars

Watchers

2 watching

Forks

Releases

Used by

Contributors

Languages