Tutorial: Your first DXEndpointBattery

This tutorial walks you through building a minimal but complete HTMX-powered feature with djxi. By the end you will have a battery that lists, creates, and deletes items — with URL routing, template sections, and Django messages all wired up.

Prerequisites

  • djxi installed and added to INSTALLED_APPS (see Installation and Setup)

  • DjxiHeadersMiddleware in MIDDLEWARE

  • A base template using {% load djxi %} / {% htmx_headers %}

Step 1 — Define the template sections

A battery’s template is split into named <dx-section> blocks. You can use either an inline string (inline_template) or a file on disk (template_name). Both support full Django template syntax.

INLINE_TEMPLATE = """
<dx-section name="item-list-container">
  <div id="item-list-container">
    <h3>Items</h3>
    <dx-include name="item-list"/>
    <dx-include name="item-form"/>
  </div>
</dx-section>

<dx-section name="item-list">
  <ul id="item-list">
    {% for item in items %}
      <li id="item-{{ item.pk }}">
        {{ item.title }}
        <button hx-delete="{% url 'items:delete' item.pk %}"
                hx-target="#item-{{ item.pk }}"
                hx-swap="outerHTML">×</button>
      </li>
    {% empty %}
      <li>No items yet.</li>
    {% endfor %}
  </ul>
</dx-section>

<dx-section name="item-form">
  <form hx-post="{% url 'items:create' %}"
        hx-target="#item-list"
        hx-swap="outerHTML">
    {% csrf_token %}
    <input name="title" placeholder="New item" required>
    <button type="submit">Add</button>
  </form>
</dx-section>
"""

<dx-include name="..."/> expands the named section inline at render time, so you can compose larger sections from smaller reusable ones.

Step 2 — Create the battery class

# myapp/views.py
from django.contrib import messages
from djxi import DXEndpointBattery, dx_get, dx_post, dx_delete

from .models import Item


class ItemBattery(DXEndpointBattery):
    inline_template = INLINE_TEMPLATE
    battery_prefix = "htmx"   # → /items/htmx/…

    # ── helpers ──────────────────────────────────────────────────────
    def _items_context(self):
        return {"items": Item.objects.order_by("-pk")}

    # ── endpoints ─────────────────────────────────────────────────────
    @dx_get("list", name="list")
    def list(self, request):
        return self.render_section(request, "item-list-container", self._items_context())

    @dx_post("create", name="create")
    def create(self, request):
        title = request.POST.get("title", "").strip()
        if title:
            Item.objects.create(title=title)
            messages.success(request, f"'{title}' added.")
        return self.render_section(request, "item-list", self._items_context())

    @dx_delete("item/<int:pk>/delete", name="delete")
    def delete(self, request, pk):
        Item.objects.filter(pk=pk).delete()
        messages.info(request, "Item removed.")
        return self.render_empty(request)

Key points:

  • render_section(request, name, context) renders the named section and returns an HttpResponse.

  • render_empty(request) returns an empty HttpResponse, used here to replace the deleted list item with nothing.

  • messages.success(...) is automatically injected into the next HTMX response as an out-of-band swap.

Step 3 — Wire up the URLs

# myapp/urls.py
from django.urls import path, include
from .views import ItemBattery

app_name = "items"

urlpatterns = [
    path("", include(ItemBattery.url_patterns())),
    # battery_prefix = "htmx" → registers:
    #   GET    /htmx/list
    #   POST   /htmx/create
    #   DELETE /htmx/item/<pk>/delete
]
# project/urls.py
urlpatterns = [
    path("items/", include("myapp.urls")),
]

Step 4 — Add the list to a page template

{# items/index.html #}
{% extends "base.html" %}
{% block content %}
  {# Bootstrap the list on page load — will be swapped in-place by HTMX #}
  <div hx-get="{% url 'items:list' %}" hx-trigger="load"></div>
{% endblock %}

Step 5 — Protecting endpoints (optional)

To restrict the battery to authenticated users, add requires_auth = True:

class ItemBattery(DXEndpointBattery):
    inline_template = INLINE_TEMPLATE
    battery_prefix = "htmx"
    requires_auth = True      # 403 for anonymous users
    login_url = "/accounts/login/"  # redirect instead of 403

See Permissions and Access Control for fine-grained access control.

Step 6 — Verify routes

python manage.py djxi_routes

Battery       URL Pattern                  Methods   Name
─────────────────────────────────────────────────────────
ItemBattery   htmx/list                    GET       list
ItemBattery   htmx/create                  POST      create
ItemBattery   htmx/item/<pk>/delete        DELETE    delete

See Management Command: djxi_routes for full command reference.

Further reading