# crispy-forms, layouts, and htmx

**URL:** <https://forum.djangoproject.com/t/crispy-forms-layouts-and-htmx/34426>\
**Category:** Forms & APIs\
**Created:** [September 2, 2024, 1:15pm UTC](https://forum.djangoproject.com/t/crispy-forms-layouts-and-htmx/34426 "2024-09-02T13:15:12Z")\
**Posts on this page:** 3\
**Page:** 1

<div class="post-metadata">

**Author:** ![idahogray](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/idahogray/32/9595_2.png) [@idahogray](https://forum.djangoproject.com/u/idahogray)\
**Post date:** [September 2, 2024, 1:15pm UTC](https://forum.djangoproject.com/t/crispy-forms-layouts-and-htmx/34426/1 "2024-09-02T13:15:12Z")

</div>

I have come at this problem a few different times and have worked around it a few different ways, none of them feeling like the correct way.

The Problem:  
I want to render a single crispy-forms field for use with htmx. The form containing the field uses a helper with a layout. The field in the layout has the `hx-` parameters defined on it. The form and field render correctly upon load. I am trying to perform field validation on each field as the user makes changes. The form data is sent to a view by htmx upon any change. The form is instantiated using the request.POST data and `is_valid` is called. I then return only the one field being checked to the browser for htmx to replace on the form. I am using the `as_crispy_field` tag to render the single field. Everything works with the one exception that the `hx-` attributes defined on the helper are not included in the rendered field.

The view code:

```python
def validate(request):
    form = CTSaturationForm(request.POST)
    form.is_valid()
    field = form[request.htmx.trigger_name]
    field.form.helper = CTSaturationFormHelper()
    return HttpResponse(as_crispy_field(field))

```

The final question:  
Does anyone know how to render a single field from a crispy-form making use of the layout defined on the containing form’s helper?

Other attempts:  
I solved this in a previous attempt, in a different part of my project, by modifying the attributes on the field’s widget before calling `as_crispy_field`. I would prefer to not have to do this manually for every field because it is already defined in the helper’s layout.

Thanks in advance.

---

<div class="post-metadata">

**Author:** ![KenWhitesell](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/kenwhitesell/32/280_2.png) [@KenWhitesell](https://forum.djangoproject.com/u/KenWhitesell)\
**Post date:** [September 2, 2024, 6:12pm UTC](https://forum.djangoproject.com/t/crispy-forms-layouts-and-htmx/34426/2 "2024-09-02T18:12:08Z")

</div>

`<conjecture>`  
From my (admittedly cursory) review of the docs and code, the use of `as_crispy_field` in this manner is covered by the same paragraph in the docs as the `crispy` filter. i.e.:

> As handy as the |crispy filter is, think of it as the built-in methods: `as_table` , `as_ul` and `as_p` . You cannot tune up the output. The best way to make your forms crisp is using the [{% crispy %} tag with forms](https://django-crispy-forms.readthedocs.io/en/latest/crispy_tag_forms.html#crispy-tag-forms).

and

> django-crispy-forms implements a class called `FormHelper` that defines the form rendering behavior. Helpers give you a way to control form attributes and its layout, doing this in a programmatic way using Python.

The implication that I draw from this is that the helpers do _not_ apply when you use the “filter-style” methods.

(This all is based on a combination of some empirical evidence and my reading of the docs, not from any direct knowledge.)  
`</conjecture>`

If I had this situation where I had different individual fields needing to be rendered, and depending upon the exact specifics involved, I’d be tempted to try one of the following:

- Dynamically create the `Layout` object as needed.
- Render the entire form and then use something like `BeautifulSoup` to extract the portion of the rendered template to be returned.

You might also want to see the thread at [Adding template fragments or partials for the DTL](https://forum.djangoproject.com/t/adding-template-fragments-or-partials-for-the-dtl/21500) for some other ideas.

---

<div class="post-metadata">

**Author:** ![idahogray](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.djangoproject.com/idahogray/32/9595_2.png) [@idahogray](https://forum.djangoproject.com/u/idahogray)\
**Post date:** [September 2, 2024, 7:31pm UTC](https://forum.djangoproject.com/t/crispy-forms-layouts-and-htmx/34426/3 "2024-09-02T19:31:27Z")

</div>

Thanks @KenWhitesell, I have come to the same conclusion. I am following the conversation you linked and I just started using django-template-partials in my project. I can’t wait to see that continue to grow.

For now, I was able to solve the problem by manually applying the attrs defined in the layout to the widget when rendering the form field.

This view gets called via htmx when any of the form fields change. It uses the `trigger_name` provided by `django-htmx` to get the form field. It then looks up the field’s location in the form helper, and merges the widget’s existing attributes with the ones from the layout. The field is then rendered using `as_crispy_field`, which is then returned to htmx. htmx then replaces the field in the form.

For completeness and documentation, here is my code.

```python
def validate(request):
    form = CTSaturationForm(request.POST)
    form.is_valid()
    field = form[request.htmx.trigger_name]
    field.form.helper = CTSaturationFormHelper()
    field_pointer = next(
        (
            pointer
            for pointer in field.form.helper.layout.get_field_names()
            if pointer.name == request.htmx.trigger_name
        )
    )
    layout_object = get_layout_child(field.form.helper.layout, field_pointer.positions)
    field.field.widget.attrs.update(layout_object.attrs)
    return HttpResponse(as_crispy_field(field))

```

An example of one of the field definitions in the helper layout is shown here.

```python
Field(
    "i_slg_ext_max",
    hx_target="#div_id_i_slg_ext_max",
    hx_post=reverse("ct-saturation-validate"),
    hx_trigger="change",
    hx_swap="outerHTML",
    hx_ext="event-header",
),

```
