1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
|
Tips
====
This page contains some tips for using htmx with Django.
.. _tips-csrf-token:
Make htmx pass Django’s CSRF token
----------------------------------
If you use htmx to make requests with “unsafe” methods, such as POST via `hx-post <https://htmx.org/attributes/hx-post/>`__, you will need to make htmx cooperate with Django’s `Cross Site Request Forgery (CSRF) protection <https://docs.djangoproject.com/en/stable/ref/csrf/>`__.
Django can accept the CSRF token in a header, normally ``x-csrftoken`` (configurable with the |CSRF_HEADER_NAME setting|__, but there’s rarely a reason to change it).
.. |CSRF_HEADER_NAME setting| replace:: ``CSRF_HEADER_NAME`` setting
__ https://docs.djangoproject.com/en/stable/ref/settings/#std:setting-CSRF_HEADER_NAME
You can make htmx pass the header with its |hx-headers attribute|__.
It’s most convenient to place ``hx-headers`` on your ``<body>`` tag, as then all elements will inherit it.
For example:
.. |hx-headers attribute| replace:: ``hx-headers`` attribute
__ https://htmx.org/attributes/hx-headers/
.. code-block:: django
<body hx-headers='{"x-csrftoken": "{{ csrf_token }}"}'>
...
</body>
Note this uses ``{{ csrf_token }}``, the variable, as opposed to ``{% csrf_token %}``, the tag that renders a hidden ``<input>``.
This snippet should work with both Django templates and Jinja.
For an example of this in action, see the “CSRF Demo” page of the :doc:`example project <example_project>`.
.. _partial-rendering:
Partial Rendering
-----------------
For requests made with htmx, you may want to reduce the page content you render, since only part of the page gets updated.
This is a small optimization compared to correctly setting up compression, caching, etc.
Using django-template-partials
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The `django-template-partials package <https://github.com/carltongibson/django-template-partials>`__ extends the Django Template Language with reusable sections called “partials”.
It then allows you to render just one partial from a template.
Install ``django-template-partials`` and add its ``{% partialdef %}`` tag around a template section:
.. code-block:: django
{% extends "_base.html" %}
{% load partials %}
{% block main %}
<h1>Countries</h1>
...
{% partialdef country-table inline %}
<table id=country-data>
<thead>...</thead>
<tbody>
{% for country in countries %}
...
{% endfor %}
</tbody>
</table>
{% endpartialdef %}
...
{% endblock main %}
The above template defines a partial named ``country-table``, which renders some table of country data.
The ``inline`` argument makes the partial render when the full page renders.
In the view, you can select to render the partial for htmx requests.
This is done by adding ``#`` and the partial name to the template name:
.. code-block:: python
from django.shortcuts import render
from example.models import Country
def country_listing(request):
template_name = "countries.html"
if request.htmx:
template_name += "#country-table"
countries = Country.objects.all()
return render(
request,
template_name,
{
"countries": countries,
},
)
htmx requests will render only the partial, whilst full page requests will render the full page.
This allows refreshing of the table without an extra view or separating the template contents from its context.
For a working example, see the “Partial Rendering” page of the :doc:`example project <example_project>`.
It’s also possible to use a partial from within a separate view.
This may be preferable if other customizations are required for htmx requests.
For more information on django-template-partials, see `its documentation <https://github.com/carltongibson/django-template-partials>`__.
Swapping the base template
~~~~~~~~~~~~~~~~~~~~~~~~~~
Another technique is to swap the base template in your view.
This is a little more manual but good to have on-hand in case you need it,
You can use Django’s template inheritance to limit rendered content to only the affected section.
In your view, set up a context variable for your base template like so:
.. code-block:: python
from django.http import HttpRequest, HttpResponse
from django.shortcuts import render
from django.views.decorators.http import require_GET
@require_GET
def partial_rendering(request: HttpRequest) -> HttpResponse:
if request.htmx:
base_template = "_partial.html"
else:
base_template = "_base.html"
...
return render(
request,
"page.html",
{
"base_template": base_template,
# ...
},
)
Then in the template (``page.html``), use that variable in ``{% extends %}``:
.. code-block:: django
{% extends base_template %}
{% block main %}
...
{% endblock %}
Here, ``_base.html`` would be the main site base:
.. code-block:: django
<!doctype html>
<html>
<head>
...
</head>
<body>
<header>
<nav>
...
</nav>
</header>
<main id="main">
{% block main %}{% endblock %}
</main>
</body>
…whilst ``_partial.html`` would contain only the minimum element to update:
.. code-block:: django
<main id="main">
{% block main %}{% endblock %}
</main>
.. _htmx-extensions:
Install htmx extensions
-----------------------
django-htmx vendors htmx and can render it with the ``{% htmx_script %}`` :doc:`template tag <template_tags>`.
However, it does not include any of `the many htmx extensions <https://htmx.org/extensions/>`__, so it’s up to you to add such extensions to your project.
Avoid using JavaScript CDNs like unpkg.com to include extensions, or any other resources.
They reduce privacy, performance, and security - see `this blog post <https://blog.wesleyac.com/posts/why-not-javascript-cdn>`__.
Instead, download extension scripts into your project’s static files and serve them directly.
Include their script tags after your htmx ``<script>`` tag (from ``{% htmx_script %}`` or otherwise).
For example, if you were using the `WebSocket extension <https://htmx.org/extensions/ws/>`__, you might:
1. Download ``ws.min.js`` from the latest release:
.. code-block:: sh
curl -L https://unpkg.com/htmx-ext-ws/dist/ws.min.js -o example/static/htmx-ext-ws.min.js
2. Include it in your base template:
.. code-block:: django
:emphasize-lines: 7
{% load django_htmx static %}
<!doctype html>
<html>
<head>
...
{% htmx_script %}
<script src="{% static 'htmx-ext-ws.min.js' %}" defer></script>
</head>
<body>
...
</body>
</html>
For another example, see the :doc:`example project <example_project>`, which includes two extensions and a Python script to download their latest versions (``download_htmx_extensions.py``).
|