Django Htmx Modal Forms
Documentation: https://django-htmx-modal-forms.readthedocs.io
Source Code: https://github.com/abe-101/django-htmx-modal-forms
A Django package that provides class-based views for handling forms in Bootstrap modals using HTMX. This package makes it easy to add create and update functionality to your Django models with a clean modal interface.
Features
- 🚀 Easy-to-use class-based views for modal forms
- ⚡ HTMX-powered for dynamic updates without page reloads
- 🎨 Bootstrap modal integration with customizable sizes
- ✨ Automatic form validation with error handling
- 🐛 Debug mode for development
Requirements
- Python 3.8+
- Django 4.2+
- django-htmx
- Bootstrap 5
- django-crispy-forms
Installation
pip install django-htmx-modal-forms
- Add to your
INSTALLED_APPS
:
INSTALLED_APPS = [
...
"crispy_forms",
"crispy_bootstrap5",
"django-htmx"
"django_htmx_modal_forms",
]
- Load and include the JavaScript in your base template:
{% load htmx_modal_forms %}
<!doctype html>
<html>
<head>
<script src="{% static 'js/bootstrap.bundle.min.js' %}"></script>
<script src="{% static 'js/htmx.min.js' %}"></script>
{% htmx_modal_script %}
</head>
<body>
</body>
</html>
Quick Start
from django_htmx_modal_forms import HtmxModalUpdateView
class PersonUpdateView(HtmxModalUpdateView):
model = Person
form_class = PersonForm
detail_template_name = "persons/_person_card.html"
modal_size = "lg"
path("persons/<int:pk>/edit/", PersonUpdateView.as_view(), name="person_update"),
- Create your detail template (
_person_card.html
):
<div id="person-{{ person.id }}" class="card">
<div class="card-body">
<h5 class="card-title">{{ person.name }}</h5>
<p class="card-text">{{ person.email }}</p>
</div>
</div>
Important: The wrapper element must have an ID that matches your model instance (e.g., id="person-{{ person.id }}"
)! This ID is used by the view to locate and replace the content after a successful form submission.
- Add a button to trigger the modal:
<button
hx-get="{% url 'person_update' pk=person.pk %}"
hx-target="body"
hx-swap="beforeend"
class="btn btn-primary"
>
Edit Person
</button>
That's it! When you click the edit button, a modal will appear with your form. On successful submission, the person's card will automatically update with the new information.
Advanced Usage
Custom Modal Titles
class PersonCreateView(HtmxModalCreateView):
model = Person
form_class = PersonForm
modal_title = "Add New Team Member"
Different Modal Sizes
class PersonUpdateView(HtmxModalUpdateView):
model = Person
form_class = PersonForm
modal_size = "xl"
Debug Mode
Debug mode is automatically enabled when settings.DEBUG = True
. It provides helpful console logging for:
- Modal initialization
- Event triggers
- Bootstrap/HTMX availability
- Error conditions
How It Works Behind the Scenes
The package orchestrates a series of interactions between Django, HTMX, and Bootstrap:
- When you click an edit button, HTMX makes a GET request to your view
- The view returns a Bootstrap modal containing your form and triggers the
modal:show
event
- The included JavaScript initializes and displays the modal
- When submitting the form:
- If there are validation errors, the view replaces the form content with the errors
- On success:
- The view updates your model
- Renders the new content using your detail template
- Uses HTMX's out-of-band swap to replace the content using the ID you provided
- Triggers the modal to close
This approach provides a smooth user experience with minimal JavaScript while maintaining Django's server-side validation and template rendering.
[Previous content remains the same until Contributing section]
Credits
This package was inspired by Josh Karamuth's blog posts on Django + HTMX modals:
Contributing
Contributions are welcome! Please feel free to submit a Pull Request. To develop locally:
- Clone the repository
- Install dependencies:
uv sync
- Run tests:
uv run pytest
Contributors ✨
Thanks goes to these wonderful people (emoji key):
This project follows the all-contributors specification. Contributions of any kind welcome!
Credits
This package was created with
Copier and the
browniebroke/pypackage-template
project template.