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 :doc:`installation`) - ``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 ```` blocks. You can use either an inline string (``inline_template``) or a file on disk (``template_name``). Both support full Django template syntax. .. code-block:: python INLINE_TEMPLATE = """

Items

{% csrf_token %}
""" ```` expands the named section inline at render time, so you can compose larger sections from smaller reusable ones. Step 2 — Create the battery class ---------------------------------- .. code-block:: python # 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//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 -------------------------- .. code-block:: python # 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//delete ] .. code-block:: python # project/urls.py urlpatterns = [ path("items/", include("myapp.urls")), ] Step 4 — Add the list to a page template ----------------------------------------- .. code-block:: html {# items/index.html #} {% extends "base.html" %} {% block content %} {# Bootstrap the list on page load — will be swapped in-place by HTMX #}
{% endblock %} Step 5 — Protecting endpoints (optional) ----------------------------------------- To restrict the battery to authenticated users, add ``requires_auth = True``: .. code-block:: python 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 :doc:`permissions` for fine-grained access control. Step 6 — Verify routes ----------------------- .. code-block:: bash python manage.py djxi_routes Battery URL Pattern Methods Name ───────────────────────────────────────────────────────── ItemBattery htmx/list GET list ItemBattery htmx/create POST create ItemBattery htmx/item//delete DELETE delete See :doc:`management` for full command reference. Further reading --------------- - :doc:`middleware` — reading and setting HTMX headers - :doc:`permissions` — ``requires_auth``, ``login_url``, ``check_permissions`` - :doc:`async` — writing ``async def`` handlers - :doc:`testing` — testing batteries with ``DXBatteryTestCase`` - The full ``todo`` example app in ``example/todo/views.py``