HTMX and Django Forms.

Hi!

I’m not very good at English, I supported the post with a translator. Sorry for that.

I think it’s something that’s been discussed before, but I’m not being able to find a solution, or a nice way to do it.

I am working with Django and HTMX. I have a View to manage records on two models, plus two forms with an inlineformset.

In the form, there are several parts that I render dynamically with HTMX. It depends on what the user selects in the select, I render other selects that by default I load empty. When the user change some values, the correspond values is render with HTMX functions.

The problem is that when you want to submit, the form is not complete. I can’t validate the form because it doesn’t contain the information I loaded dynamically with HTMX.

At this point I’m trying different approaches.

  1. The HTMX view returns an HTML that is rendered by a dictionary with the dynamic values.

At this point the problem is that the form is not aware of the available values, nor of course the selected value.

To validate it, i try modifying the queryset with the values ​​from the post in the view, manually. It generates multiple problems, in addition to not being able to render a form with the selected values ​​in case of a server-side error.

  1. The HTMX view returns an HTML that is rendered by a form.

At this point I need to pass the values ​​already selected in the GET to the HTMX view, so I can create the complete form and replace it in the DOM. I think it can work, but I don’t like the idea.

So, what do you recommend I do, research or read?

If you think some code can help, I can add something. But I don’t want to make too much noise, I understand that it must be something super resolved with some pattern or something that I’m overlooking.

Thank you so much!

Welcome @blauget !

This can be handled in your form’s __init__ method by checking the value of the first select box and then setting the legal values for the second select box based upon that first value.

If that is causing you problems, then it would be helpful if you posted the code for the form here.

I’m not sure I’m following what you’re trying to describe for #2 or what the problem is there. Again, it may be more helpful if you posted the code involved and explain specifically where you’re having an issue.

Oh, and it’s no problem with using translators, I do it frequently when people create their posts here in their native language.

1 Like

Hi Ken!

First of all I would like to thank you for your support of this community. I haven’t participated in the forum before, but I read many of your responses and found a lot of useful material in many cases. Thank you!

I don’t quite understand the init modifications on forms. If the values ​​are changed dynamically on the front end with HTMX, how will the form know to render the different values?

What I have partially functional is:

To set up the form so that the user can enter data:

  1. Form.
class ActivityFormV4(forms.ModelForm):
    class Meta:
        model = Activity
        fields = [
            'activity_type',
            'enterprise',
            'establishment',
            'paddocks',
            'campaign',
        ]

        widgets = {
            'paddocks': forms.CheckboxSelectMultiple(),
        }

    def __init__(self, *args, **kwargs):
        user = kwargs.pop('user', None)
        super().__init__(*args, **kwargs)

        if user:
            user_profile = get_object_or_404(UserProfile, user=user)
            allowed_enterprises = EnterpriseClient.objects.filter(
                enterprise=user_profile.enterprise
            ).values_list('client', flat=True)

            self.fields['enterprise'].queryset = Enterprise.objects.filter(id__in=allowed_enterprises)
            self.fields['establishment'].queryset = Establishment.objects.none()
            self.fields['paddocks'].queryset = Paddock.objects.none()

The values ​​as establishment, I load them empty because they will depend on what the user selects in enterprise.

  1. Principal Template.
    Within the main template, I call a view in case the company value changes.
<select id="enterprise" name="enterprise" class="form-control"
                                            hx-get="{% url 'htmx_establishments_list' %}"
                                            hx-target="#establishment-container"
                                            hx-trigger="change"
                                            required
                                    >
                                        {% for value, label in form.enterprise.field.choices %}
                                            <option value="{{ value }}" {% if form.enterprise.value == value %}selected{% endif %}>
                                                {{ label }}
                                            </option>
                                        {% endfor %}
                                    </select>
  1. HTMX View.

In the htmx View, i get the objects to render the partials template.

def htmx_establishments_list(request):
    enterprise_id = request.GET.get('enterprise')
    establishments = Establishment.objects.filter(enterprise_id=enterprise_id)
    return render(request, 'partials/establishment_widget.html',
                  {'establishments': establishments})
<select id="id_establishment" name="establishment" class="form-control"
     hx-get="{% url 'htmx_paddocks_list' %}"
    hx-target="#paddock-container"
    hx-trigger="change"
    required
        >
    <option value="" selected>Seleccione un establecimiento</option>
    </option>
    {% for establishment in establishments %}
        <option value="{{ establishment.id }}" {% if establishment.id == value %}selected{% endif %}>
            {{ establishment.name }}
        </option>
    {% endfor %}
</select>

Up to this point everything works as I expect, I can render the different sections. But the problem arises when trying to validate the form. All the values ​​that I render with HTMX are not inside the form.

I tried returning a form in the HTMX view, instead of a dictionary with only the values, but I think it overwrites the initial form, which causes other problems.

Thanks again!!

That’s why you want to modify the form in the __init__ method when you’re binding the data to the form in the POST processing.

You are correct, when you are creating the instance of the form during the GET processing, you don’t know what values to retrieve - and that’s ok, because no validation occurs during the GET.

However, when you are handling the submit in the POST, and are binding the data to the form, that is when you want to modify the values, based upon what is provided in the post.

This means that you could do something like this in your __init__ method:
(Note: Untested and “iffy” - this may need to be changed.)

enterprise = self.fields['enterprise'].to_python(self.data.get('enterprise',None))
self.fields['establishment'].queryset = Establishment.objects.filter(enterprise=enterprise)

The data being posted to the form is stored in the data attribute of the form as a dict. In the case of the selection of a model, it’s the PK of that instance that is submitted. The to_python method of that ModelChoiceField will execute the query to retrieve the instance.

Note, if you are willing to assume that the data being submitted is valid - generally a bad idea but not horrible in this case, you can avoid the query by using the submitted value for enterprise directly without the additional query:

self.fields['establishment'].queryset = Establishment.objects.filter(enterprise_id=self.data.get('enterprise', None))