Skip to main content

django-htmx plus some extras

Project description

django-htmx-plus

A Django utility library that works with django-cotton to provide ready-made views, mixins, middleware, response helpers, and components for building HTMX-powered list views with filtering, sorting, and pagination.

This package was created for my own projects but it was handy enough to share, I learned how to use HTMX with Django and Boostrap Modals from the following two posts from Josh Karamuth:


Features

  • HtmxResponse – a 204 No Content response that fires HX-Trigger events on the client.
  • HtmxRedirectResponse – a response that sends an HX-Redirect header to navigate the browser.
  • HtmxMessagesMiddleware – automatically forwards Django messages to the client via the HX-Trigger header on every HTMX request.
  • HtmxFormResponseMixin – a FormView/CreateView mixin that replaces the success redirect with an HTMX trigger response.
  • HtmxListView – a ListView subclass with built-in URL-based filtering, column sorting, and elided pagination.
  • Filter helpers – parse field.filter_type=value query parameters safely into Django ORM filter dicts.
  • Cotton components – drop-in table, header cell, and pager components styled for Bootstrap 5.
  • Template filtersget_attr and get_key_value for accessing object attributes and dict values in templates.
  • Built-in JavaScript helper – a django-htmx-plus.js ES module that wires Bootstrap 5 Modals and Offcanvases to HTMX swap events, with a {% htmx_plus_script %} template tag to include it.

Requirements

  • Python 3.11+
  • Django (any version compatible with the above)
  • django-cotton ≥ 2.6

Installation

pip install django-htmx-plus

Add to INSTALLED_APPS and configure middleware in settings.py:

INSTALLED_APPS = [
    # ...
    "django_cotton",
    "django_htmx_plus",
]

MIDDLEWARE = [
    # ...
    "django_htmx_plus.middleware.HtmxMessagesMiddleware",
]

Usage

HtmxResponse

Return a 204 No Content response that triggers one or more HTMX events on the client:

from django_htmx_plus.http import HtmxResponse

def my_view(request):
    # ... do some work ...
    return HtmxResponse(triggers=["itemUpdated", "refreshStats"])

The response sets HX-Trigger: itemUpdated,refreshStats, which HTMX picks up to re-fetch any elements listening for those events.


HtmxRedirectResponse

Instruct HTMX to navigate the browser to a new URL without a traditional HTTP redirect:

from django_htmx_plus.http import HtmxRedirectResponse

def my_view(request):
    return HtmxRedirectResponse(destination="/dashboard/")

HtmxMessagesMiddleware

Once the middleware is installed, any Django message added during an HTMX request is automatically serialised into the HX-Trigger header as a messages key:

{
  "messages": [
    {"message": "Record saved.", "tags": "success"}
  ]
}

If the view already sets its own HX-Trigger header (either string or JSON object syntax), the middleware merges the messages in without overwriting existing triggers.

Listen for the messages event in your HTMX setup to display the messages, for example:

document.body.addEventListener("messages", (event) => {
    event.detail.value.forEach(({message, tags}) => {
        showToast(message, tags); // your toast implementation
    });
});

HtmxFormResponseMixin

Mix into any FormView or CreateView to replace the default success redirect with an HTMX trigger response:

from django.views.generic.edit import CreateView
from django_htmx_plus.mixins import HtmxFormResponseMixin
from myapp.models import Article
from myapp.forms import ArticleForm

class ArticleCreateView(HtmxFormResponseMixin, CreateView):
    model = Article
    form_class = ArticleForm
    template_name = "articles/form.html"

    valid_triggers = ["articleCreated"]
    success_message = "Article created successfully."

After a valid submission, form.save() is called, an optional Django success message is queued, and an HtmxResponse carrying articleCreated in HX-Trigger is returned.

Attribute Type Description
valid_triggers List[str] Events to include in HX-Trigger on success.
success_message str Optional Django success message to queue.

HtmxListView

A drop-in replacement for Django's ListView that adds URL-driven filtering, sorting, and elided pagination:

from django.views.generic import ListView
from django_htmx_plus.views import HtmxListView
from myapp.models import Article

class ArticleListView(HtmxListView):
    model = Article
    template_name = "articles/list.html"
    paginate_by = 20
    target_id = "#article-table"

    # Restrict filtering and sorting to these fields only
    fields = ("id", "title", "status", "created_at")

    # Optional custom column labels
    labels = {
        "created_at": "Date Created",
    }

URL-based filtering

Filters are expressed as field_name.filter_type=value query parameters:

Filter key Django lookup Example
eq exact status.eq=published
ieq iexact title.ieq=hello
gt / gte / lt / lte gt / gte / lt / lte views.gte=100
like / ilike like / ilike title.ilike=django
sw / isw startswith / istartswith title.sw=Django
ew / iew endswith / iendswith title.ew=plus
in in status.in=['draft','published']
nl isnull deleted_at.nl=True
rng range created_at.rng=['2024-01-01','2024-12-31']
sch search body.sch=htmx

Only fields listed in fields are accepted. Setting fields = ("__all__",) lifts the restriction.

Sorting

Add order_by=field_name (or order_by=-field_name for descending) to the query string. Only fields in fields are permitted.

Context variables

Variable Description
query_params URL-encoded query string (without page).
page_range Elided page range for pagination controls.
order_by The currently active ordering field.
path The current request path.
target_id The HTMX target element ID.
query Full query string including order_by.
filter_query Query string containing only filter parameters.
filters Template-ready dict mapping plain field names to their filter values.
fields Dict with keys (field names) and labels (display names).

Cotton Table Components

django-htmx-plus ships a set of django-cotton components for rendering sortable, paginated HTMX tables with Bootstrap 5.

<c-tables.htmx_table />

Renders a full table with auto-generated sortable headers, rows, and an optional pager:

{% load cotton_extras %}

<c-tables.htmx_table class="table table-striped" />

The component uses the fields, objects, order_by, path, filter_query, target_id, page_obj, and paginator context variables provided automatically by HtmxListView.

<c-tables.header_cell name="field_name" />

Renders a single <th> with an hx-get attribute that toggles ascending/descending order and a chevron icon indicating the current sort direction:

<c-tables.head>
    <c-tables.header_cell name="title">Title</c-tables.header_cell>
    <c-tables.header_cell name="created_at">Date</c-tables.header_cell>
</c-tables.head>

<c-tables.pager />

Renders a Bootstrap 5 pagination control with previous/next buttons and elided page numbers, all wired to hx-get.

Icon components

Component Description
<c-icons.chevron_up /> Up chevron (ascending sort indicator).
<c-icons.chevron_down /> Down chevron (descending sort indicator).
<c-icons.chevron_left /> Left chevron (previous page).
<c-icons.chevron_right /> Right chevron (next page).

All icon components accept an optional add_class attribute to append extra CSS classes.


Template filters

Load the cotton_extras tag library in any template:

{% load cotton_extras %}
Filter Description Example
get_attr Get an attribute from an object by name. {{ item|get_attr:"title" }}
get_key_value Get a value from a dict by key. {{ my_dict|get_key_value:"name" }}

Built-in JavaScript Helper

django-htmx-plus ships a small ES module (django-htmx-plus.js) that integrates Bootstrap 5 Modals and Offcanvases with HTMX swap events, so they open and close automatically based on HTMX responses.

How it works

Behaviour Trigger
Show a Modal or Offcanvas HTMX swaps content into the element → htmx:afterSwap fires and calls .show().
Hide a Modal or Offcanvas HTMX receives an empty response targeting the element → htmx:beforeSwap fires, calls .hide(), and cancels the swap.
Reset Modal body Bootstrap's hidden.bs.modal event fires → the modal body is cleared to "".

Setup

Mark your Bootstrap Modal root elements with data-htmx-plus-modal="<id>" and your Offcanvas root elements with data-htmx-plus-offcanvas="<id>", where id is the id of the element that will be swapped,:

<!-- Bootstrap Modal managed by django-htmx-plus -->
<div class="modal fade" data-htmx-plus-modal="dialog">
    <div id="dialog" class="modal-dialog">
        <!-- Modal content will be swapped here by HTMX and shown/hidden by django-htmx-plus.js -->
    </div>
</div>

<!-- Bootstrap Offcanvas managed by django-htmx-plus -->
<div id="flyout" class="offcanvas offcanvas-end" data-htmx-plus-offcanvas="flyout">
    <!-- Offcanvas content will be swapped here by HTMX and shown/hidden by django-htmx-plus.js -->
</div>

Then point an HTMX element at the matching target ID:

<button hx-get="/person/add/" hx-target="#dialog">
    Add Person
</button>
  • When the response has content the modal/offcanvas is shown automatically.
  • When the server returns an empty 200 (or you use HtmxResponse) the modal/offcanvas is hidden automatically.

Including the script

Use the {% htmx_plus_script %} template tag to render the <script> tag. The script is loaded as an ES module and optionally forwards a CSP nonce if one is present in the template context:

{% load django_htmx_plus %}

<!-- Place near the bottom of your base template, after Bootstrap JS -->
{% htmx_plus_script %}

This renders:

<script src="/static/django_htmx_plus/django-htmx-plus.js" type="module"></script>

If a nonce variable is present in the template context it is automatically added as a nonce="..." attribute.

Note: The script imports Modal and Offcanvas from bootstrap, so Bootstrap 5 must be available as an ES module (e.g. via an import map or a bundler). If you load Bootstrap as a plain global script instead, adjust your bundler or import map accordingly.

For example

<script type="importmap">
{
  "imports": {
	"@popperjs/core": "{% static '@popperjs/core/dist/esm/index.js' %}",
	"bootstrap": "{% static 'bootstrap/js/index.esm.js' %}",
  }
}
</script>

License

MIT — see LICENSE for details.

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

django_htmx_plus-0.0.0.tar.gz (17.1 kB view details)

Uploaded Source

Built Distribution

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

django_htmx_plus-0.0.0-py3-none-any.whl (19.8 kB view details)

Uploaded Python 3

File details

Details for the file django_htmx_plus-0.0.0.tar.gz.

File metadata

  • Download URL: django_htmx_plus-0.0.0.tar.gz
  • Upload date:
  • Size: 17.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for django_htmx_plus-0.0.0.tar.gz
Algorithm Hash digest
SHA256 5bfdbaf16a9ede35752b80a65a7b405f4760e736009f0b1f8617e2519e17a043
MD5 ecbb1c72ba8ed7657b3ba614de89a061
BLAKE2b-256 fa0cf295c9def73528041d04281c7cf29c1d8b8c2896a6523bb4527028cce8ac

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_htmx_plus-0.0.0.tar.gz:

Publisher: release.yml on timthedevguy/django-htmx-plus

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

File details

Details for the file django_htmx_plus-0.0.0-py3-none-any.whl.

File metadata

File hashes

Hashes for django_htmx_plus-0.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 3d37b9786653e5da494694c5efc43955ab446e02e3cb9a27eddab9e6b5eea018
MD5 bff52a6a738a60c78456c563655edc57
BLAKE2b-256 bdfe67f9770399f80757af2d257f914fec2c6e0fe3ac4a04588796a193521133

See more details on using hashes here.

Provenance

The following attestation bundles were made for django_htmx_plus-0.0.0-py3-none-any.whl:

Publisher: release.yml on timthedevguy/django-htmx-plus

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 Pingdom Monitoring Sentry Error logging StatusPage Status page