1

There are several writeups on how to use javascript to add forms dynamically to a Django formset. For example this or this.

I just learned about htmx.

How can I add forms dynamically to a Django formset using htmx instead of hand-coded javascript? I thought maybe I could use click-to-load, but it seems odd to call back to the server to get an empty row. Maybe this just isn't an htmx thing.

dfrankow
  • 20,191
  • 41
  • 152
  • 214

3 Answers3

2

With HTMX, instead of the complication of formsets, you can dynamically add forms, and swap in the resulting model instance on submit.

This example todo app provides a demonstration of the approach. It allows you to add additional items by appending a form, and upon submitting successfully, swaps in the model instance resulting from that form submission. The end result is much like using formsets.

djangomachine
  • 619
  • 4
  • 15
1

I just answered a similar question here: The right way to dynamically add Django formset instances and POST usign HTMX?

Here's the summary of my approach:

  1. Create a view function to bring in your new formset via an hx-get.

  2. In that view function, build the html of your new formset by starting with YourFormset.empty_form to get the basic html, and then replace the default "__prefix__" with the number representing the index the new formset should have in its id, name and label attributes. For example, if it's the second formset on the page its id will be id_form-1-title (changed from id_form-__prefix__-title).

  3. In the html for the new formset, also include a hidden input element to replace the one creted by django's ManagementForm to update the value of form-TOTAL_FORMS. Place hx-swap-oob="true" on that input so that it replaces the old element.

A longer explanation with example code is at: The right way to dynamically add Django formset instances and POST usign HTMX?

Docs reference: https://docs.djangoproject.com/en/4.1/topics/forms/formsets/

Matt
  • 133
  • 1
  • 6
0

A trick you can use here is that Django allows you to change the formset's min_num after it's created, letting you return any form index in a formset. The thing that you want to substitute with htmx is the new form and the management form, so you can create a view returning those:

def new_form(request: HttpRequest, i: int):
    formset = MyFormset()
    formset.min_num = i + 1
    context = {'next': i + 1,
               'form': formset.forms[i],
               'management_form': formset.management_form}
    return render(request, 'myapp/partial/new_form.html', context)

where the template looks like

<div class="form">{{ form }}</div>
<div id="new_form">
    {{ management_form.TOTAL_FORMS }}
    <button hx-target="#new_form" hx-get="/new_form/{{ next }}">
        Add Form
    </button>
</div>

Notice how the management form is inside the thing htmx replaces.

This approach keeps the benefit of formsets that they are all submitted at once rather than individually.

Dan
  • 12,409
  • 3
  • 50
  • 87