Saving a Model won't work with HTMX

HI all,

I followed several examples from different tutorials, and I can’t get my form to update the DB model when using Django and HTMX

I have an HTML page (see code below), which use HTMX to add a form from another HTML page:

{% extends "base.html" %}
{% load crispy_forms_tags %}
{% load static %}

{% block title %}Client Information {% endblock %}

{% block content %}
  <h1> Add Lead to Sales Forecast </h1>
  <p>Click the Button below to add a Payment Date</p>


    <button type="button" hx-get="{% url 'lead_entry_forecast' %}" hx-target="#leadform" hx-swap="beforeend" >
        Add Lead  </button>

    <div id="leadform">

    </div>
     <script>
        document.body.addEventListener('htmx:configRequest', (event) => {
            event.detail.headers['X-CSRFToken'] = '{{ csrf_token }}';
        })
    </script>


{% endblock %}

Form

{% load crispy_forms_tags %}


    <div class="container-fluid" hx-target="this" hx-swap="outerHTML">
        <form method="POST">
            {% csrf_token %}
            <div class="row justify-content-center">
                <div class="col-sm-1">
                {{ form.lead_id|as_crispy_field }}
                </div>
                <div class="col-sm-2">
                {{ form.date|as_crispy_field }}
                </div>
                <div class="col-sm-2">
                {{ form.revenue|as_crispy_field }}
                </div>
                <div class="col-sm-1">
                {{ form.probability|as_crispy_field }}
                </div>
                <div class="col-sm">
                    <button type="submit" class="buttons"  hx-post="."> Submit </button>
                </div>

            </div>

        </form>
    </div>
    <hr>

My form is inside this HTML, and essentially every time you click the button on the first page there are several forms presented in the page, the main problem is that when I submit the data for any of those forms, I get no error, but it actually doesn’t save the form data.

I have tested the individual form, and I managed to successfully update the database, so I’m not sure why when the form is rendered via add_to_forecast.html, the post is not even send

Django Views

## THIS VIEW PRESENTS THE FIRST HTML CODE
@login_required
def add_to_forecast(request):
    id = request.GET.get('project_id')
    request.session['lead_id'] = id
    return render(request, 'account/lead_forecast.html')

## THIS GENERATES THE FORM
@login_required
def forecast_form(request):
    if request.method=='POST':
        form = LeadEntryForm(data=request.POST)
        if form.is_valid():
            form.save()
            messages.success(request,"Successful Submission")
            return redirect('add_to_forecast')

   #Filling some information in the form
    lead_id = request.session['lead_id']
    data = {'lead_id':lead_id}

    context = {
        "form":LeadEntryForm(initial=data)
    }
    return render(request, 'account/lead_entry_forecast.html', context)

Are you saying you’re not even seeing the “POST” in your server log?

Can you check your browser’s developer tools, network tab, to verify that nothing is even trying to be sent?

That doesn’t seem to match up with what the docs say should be used in “hx-post”. I’m not seeing any reference in the docs for what setting hx-post="." is supposed to do.

My gut reaction is that this is going to try and post back to the page URL, not the form’s view url. (But that’s 100% conjecture with no idea as to the accuracy of that guess.)

Also, I don’t understand what you’re trying to show as the flow of events here.

What view presents the page?

What view presents the forms?

What view is supposed to be posted to by the forms?

What are the URLs for each of those views?

Ken,

Thanks for your prompt response, please see my answer below:

  • Correction - I can see the POST request in the server log:
    [05/Dec/2021 11:01:48] “POST /account/add_to_forecast/ HTTP/1.1” 200 1898

  • The hx-post ="." is meant to force the post to stay in the view, if I remove that it will essentially refresh the page, ultimately, I want the user to see what he just submitted, but also to have the option to add additional data, obviously, at the moment, I can’t redirect the user to another HTML, as the form is not submitting data, eventually, that hx-post will point to another URL - If I removed the hx-post, the form still doesn’t work.

What view presents the page?


@login_required
def add_to_forecast(request):
    id = request.GET.get('project_id')
    request.session['lead_id'] = id
    return render(request, 'account/lead_forecast.html')

What view presents the forms?

## THIS GENERATES THE FORM
@login_required
def forecast_form(request):
    if request.method=='POST':
        form = LeadEntryForm(data=request.POST)
        if form.is_valid():
            form.save()
            messages.success(request,"Successful Submission")
            return redirect('add_to_forecast')

   #Filling some information in the form
    lead_id = request.session['lead_id']
    data = {'lead_id':lead_id}

    context = {
        "form":LeadEntryForm(initial=data)
    }

What view is supposed to be posted to by the forms?
At the moment it points back to the main view - see image below

What are the URLs for each of those views?

  path('add_to_forecast/',views.add_to_forecast,name='add_to_forecast'),
  path('lead_entry_forecast/',views.forecast_form,name='lead_entry_forecast'),

So my guess was correct, you’re posting to the main page view, not the forms’ view.

Your add_to_forecast view isn’t handling the post.

1 Like

Mark,

You are amazing, of course, I have to move the logic to process the form from lead_entry to add_forecast:

@login_required
def add_to_forecast(request):
    form = LeadEntryForm(data=request.POST or None)
    if request.method == 'POST':
        if form.is_valid():
            form.save()
            return HttpResponse("Success")
        else:
            return render(request, 'account/lead_entry_forecast.html', {"form": form})

    id = request.GET.get('project_id')
    request.session['lead_id'] = id
    return render(request, 'account/lead_forecast.html')


@login_required
def forecast_form(request):

    lead_id = request.session['lead_id']
    data = {'lead_id':lead_id}

    context = {
        "form":LeadEntryForm(initial=data)
    }
    return render(request, 'account/lead_entry_forecast.html', context)
    ```

Side note: It looks like you’re creating multiple instances of the same form. In Django, this is typically a Use-case for a formset. You may have fewer issues if you build this around that instead of trying to manage multiple discrete forms.

1 Like

Ken,

Thanks, I started with Formsets, but got some inspiration from here:

Django Formsets Tutorial - Build dynamic forms with Htmx (justdjango.com)

The spilled knowledge from that tutorial is that if you are using HTMX:

"Ultimately, the solution to achieving dynamic form logic with Htmx is to not use formsets. As you’ve seen in this tutorial so far we haven’t used formsets at all when dealing with Htmx.

While this solution might not end up with exactly the result you were looking for, in my experience the things that matter are:

How understandable and maintainable is the code?
Does the desired outcome solve the problem?"

I didn’t actually use the tutorial code, basically, I had a similar problem to solve, so I use that information as my main source of “how-to”

And that really is the bottom line. If the HTMX solution provides all the functionality you need, great!

Rather than do this, you can use hx-headers. See here for a demo: https://github.com/adamchainz/django-htmx/blob/main/example/example/templates/csrf-demo.html . (And check out the example app in general.)

2 Likes