Skip to content

{% hput %}

Concept

hput customizes a BoundField for rendering, with option to supply inline validation using the logic derived from hernantz.

Invocation via Django Template Language
1
2
3
4
5
<form method="post" action="{% url 'account_signup' %}">
  {% csrf_token %}
  {% hput form.email validate="/this-is-an-endpoint" %}
  ...
</form>
Output HTML after the Template is populated with the Context.
<form method="post" action="/accounts/signup/">
  <input type="hidden" name="csrfmiddlewaretoken" value="xxx">
  <div id="hput_id_email"
    hx-select="#hput_id_email"
    hx-post="/this-is-an-endpoint"
    hx-trigger="blur from:find input"
    hx-target="#hput_id_email"
    hx-swap="outerHTML"
    class="h"
    data-widget="email">
    <label for="id_email">Email</label>
    <input type="email" name="email" required id="id_email">
    <small>Testable form</small>
  </div>
  ...
</form>

Use of django_widget_tweaks inlined with {% hput ... %} works:

Invocation via Django Template Language
1
2
3
4
5
6
{% load widget_tweaks %}
<form method="post" action="{% url 'account_signup' %}">
  {% csrf_token %}
  {% hput field=form.email|attr:"placeholder=Hello World!" %}
  ...
</form>
Output HTML after the Template is populated with the Context.
1
2
3
4
5
6
7
8
9
<form method="post" action="/accounts/signup/">
  <input type="hidden" name="csrfmiddlewaretoken" value="xxx">
  <div> {# how the input field is rendered #}
    <label for="id_email">E-mail</label>
    <input type="email" name="email" placeholder="Hello World!" autocomplete="email" required id="id_email">
    <small>Testable form</small>
  </div>
  ...
</form>

hx_enable_inline_validation()

Requires htmx-compatible view

Need to handle the request, checking if it is an htmx request (see is_htmx()) and then render the template with the form.

Given a BoundField and a url, implement inline field validation proposed by hernantz. It makes use of various tags from htmx.

The gist: form is prematurely submitted (because of hx-post) as an AJAX-request, the partial template response will hx-swap an hx-target. In other words, the old field is replaced by the same field... but now as a result of form.is_valid().

The response, if it contains errors, will include an error list for the field.

Instead of rendering the entire partial response, the use of hx-select limits the replacement to a segment of the partial response.

Parameters:

Name Type Description Default
bound BoundField

A Django field with data previously submitted

required
url str

Where the form is submitted.

required

Returns:

Name Type Description
str str

A string of text attributes that can be added to a wrapping div of a BoundField

Source code in django_fragments/templatetags/helpers.py
Python
def hx_enable_inline_validation(bound: BoundField, url: str) -> str:
    """Given a `BoundField` and a `url`, implement inline field validation proposed by
    [hernantz](https://hernantz.github.io/inline-form-validation-with-django-and-htmx.html).
    It makes use of various tags from [htmx](https://htmx.org).

    The gist: form is prematurely submitted (because of `hx-post`) as an AJAX-request,
    the partial template response will `hx-swap` an `hx-target`. In other words, the
    old field is replaced by the same field... but now as a result of `form.is_valid()`.

    The response, if it contains errors, will include an error list for the field.

    Instead of rendering the entire partial response, the use of `hx-select` limits the
    replacement to a segment of the partial response.

    Args:
        bound (BoundField): A Django field with data previously submitted
        url (str): Where the form is submitted.
    Returns:
        str: A string of text attributes that can be added to a wrapping div of a `BoundField`
    """  # noqa: E501
    idx = f"hput_{bound.id_for_label}"
    return attrize(
        {
            "id": idx,
            "hx-select": f"#{idx}",
            "hx-post": url,
            "hx-trigger": "blur from:find input",
            "hx-target": f"#{idx}",
            "hx-swap": "outerHTML",
        }
    )