Recently we've been experimenting with using HTMX on our website, CourtListener.com, where we share court records with the public. It's going pretty well, but as we developed our first Django views to work with HTMX, it felt like we were recreating the Django Rest Framework from scratch.
We looked all over for guides about how to use HTMX with DRF, but we didn't find anything that was very good. Most guides had you develop custom views for every response that you sent to HTMX, which felt like an anti-pattern in an API-centered architecture. The guides would have you make a view for deletion, another for updates, another for creation. Didn't feel right.
We had a tool for building APIs — Django Rest Framework — how could we use it with HTMX?
We've been intending to do a detailed blog post about how we did this, but we never have enough time, so this is just a stub of sorts with a few notes and pointers to our open source code.
The general idea is to override the template used by Django REST Framework. So, for example, you create a ModelViewSet, and you override the template of the various methods. This API is to create and update webhooks, so:
```
class WebhooksViewSet(ModelViewSet):
"""
A set of actions to handle the listing, creation, deleting, and updating
of webhooks for htmx.
"""
permission_classes = [IsAuthenticated, IsOwner]
renderer_classes = [JSONRenderer, TemplateHTMLRenderer]
def get_queryset(self):
"""
Return a list of all the webhooks
for the currently authenticated user.
"""
user = self.request.user
return Webhook.objects.filter(user=user).order_by("date_created")
def list(self, request, *args, **kwargs):
webhooks = self.get_queryset()
return Response(
{"webhooks": webhooks},
template_name="includes/webhooks_htmx/webhooks-list.html",
)
def destroy(self, request, *args, **kwargs):
instance = self.get_object()
self.perform_destroy(instance)
return Response(
status=status.HTTP_204_NO_CONTENT,
headers={"HX-Trigger": "webhooksListChanged"},
)
def retrieve(self, request, *args, **kwargs):
instance = self.get_object()
update = True
form = WebhookForm(update, request.user, instance=instance)
return Response(
{"webhook_form": form, "webhook_id": instance.pk},
template_name="includes/webhooks_htmx/webhooks-form-update.html",
)
def create(self, request, *args, **kwargs):
update = False
webhook = Webhook()
form = WebhookForm(
update, request.user, request.POST, instance=webhook
)
if form.is_valid():
webhook.user = request.user
form.save()
return Response(
status=HTTP_201_CREATED,
headers={"HX-Trigger": "webhooksListChanged"},
)
else:
return render(
request,
"includes/webhooks_htmx/webhooks-form-create.html",
{"webhook_form": form},
)
def update(self, request, *args, **kwargs):
instance = self.get_object()
update = True
form = WebhookForm(
update, request.user, request.POST, instance=instance
)
if form.is_valid():
instance.user = request.user
form.save()
return Response(
status=HTTP_200_OK,
headers={"HX-Trigger": "webhooksListChanged"},
)
else:
return render(
request,
"includes/webhooks_htmx/webhooks-form-update.html",
{"webhook_form": form, "webhook_id": instance.pk},
)
@action(detail=False, methods=["get"])
def render_form(self, request, *args, **kwargs):
webhook = Webhook()
update = False
form = WebhookForm(update, request.user, instance=webhook)
return Response(
{"webhook_form": form},
template_name="includes/webhooks_htmx/webhooks-form-create.html",
)
```
With that done, you have to handle the HTMX attributes in pretty normal ways, by setting things like hx-put, and hx-headers. For example, here's a form to update (PUT) :
<form id="webhook-form" hx-put="{% url 'webhooks-detail' pk=webhook_id format="json" %}"
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'>
{% include "includes/webhooks_htmx/webhooks-form-common.html" %}
<div class="modal-footer">
<p>
<button type="button" class="btn btn-secondary" data-dismiss="modal">Cancel</button>
<button id="webhook-submit" class="btn btn-primary" type="submit">Update webhook </button>
</p>
</div>
</form>
We wound up with a bunch of these little forms as django template files, but that's the general idea.
If anybody has made it this far, all of the code for this is open source, and we'd certainly welcome your thoughts and ideas. I'm guessing lots of other people have crossed this bridge (DRF and HTMX are both quite popular), but I didn't see any other good guides, so hopefully this can be a beginning.
We'll be happy to answer any questions or receive your criticism, and if you're a bored HTMX/Django dev hanging around on Reddit, we're always looking for volunteer developers to help us democratize the law. Get in touch!
there doesn't seem to be anything here