Multiple ModelForm on a same page

Before I scrap my half a day worth of work…
Got a page with 13 ModelForm, each have 1 or 2 field, basic text input (names of object types)
My idea was to load all when page loaded, hide all by default, show the form what needed (side menu onclick function remove attribute hidden), user submit form, when succes return, clear form and hide it again.

        <div id="divForm1" class="customForms mx-1 mt-3" hidden>
            <span class="label label-dark mt-1">Form1</span>
            <form method="post" id='formForm1' name="formForm1" enctype="multipart/form-data">
                {% csrf_token %}
                {{formForm1|crispy}}
                <button class="btn btn-primary my-4" id="submitForm1" type="button">Submit</button>
            </form>
        </div>
        <div id="divForm2" class="customForms mx-1" hidden>
            <form method="post" id='formForm2' name="formForm2" enctype="multipart/form-data">
                {% csrf_token %}
                {{formForm2|crispy}}
                <button class="btn btn-primary my-4" name="submitForm2" type="submit">Submit</button>
            </form>
        </div>

in the form

        class Model1Form(forms.ModelForm):
			class Meta:
				model = Model1
				fields = '__all__'

in the view

        context = {}
		formModel1Form = Model1Form(request.POST or None)
		formModel2Form = Model2Form(request.POST or None)
		if 'formModel1Form' in request.POST:
            if formModel1Form.is_valid():
               formModel1Form.save()
        if 'formModel2Form' in request.POST:
		    if formModel2Form.is_valid():
               formModel2Form.save()
			
		context['formModel1Form'] = formModel1Form
		context['formModel2Form'] = formModel2Form
		
		return render(request, 'options/test.html', context)

unfortunately submitting 1 of 13 form will create 12 invalid (incomplete) form notifications.

is there a way to load 13 from dynamically in to the same page (not at once) without 13 url path and 13 view ? and possible more for 13 edit and 13 delete ::
I think its show what a noob im ::
–fixed indent, added 2nd if –

It’s entirely up to you how to handle uncompleted forms.

If a form fails the is_valid check, it’s your choice what to do about it.

If the standard patterns don’t work for you, then take different actions.

is there a way to dynamically create form in page? like in html have 1 {{ form }} and load the requested form1, form2 or form9 ? google not giving me much help, probably I don’t describe my search properly or no such a thing exist

Yes and no.

Django runs in the server, not in the browser. So all Django form creation occurs in the server.

However, you can effectively do what you want by having the browser request a form from the server.
It would be an AJAX style call in the browser combined with a view that renders the form and returns the form as HTML to the browser. The AJAX call then replaces the appropriate div in the DOM with that HTML.

used fect many times, I don’t see how to automate request of 10+ form and than saving them dynamically
what I have tried so far,

def form_view(request):
     form = Model1Form()
     if form.is_valid()
        form.save()
     
     context = { "form": form,}
     template = render_to_string("/forms.html", context=context)
     return JsonResponse({"form":template})

froms.html
    <form action="/forms/" method="POST" name="form">
        <input id="csrfTokken" type="hidden" name="csrfmiddlewaretoken" value=""/>
        {{ form|crispy}}
        <button class="btn btn-primary btn-outline my-3" type="submit">Save</button>
    </form>

in main.html
<div class="form-container"></div>

<script>
    const url = "{% url 'get_forms' %}";
    const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value;

    fetch(url)
      .then(function (response) {
        return response.json();
      })
      .then(function (data) {
        const formContainer = document.querySelector(".form-container");
        formContainer.innerHTML = data.form;
        document.querySelector("#csrfTokken").value=csrftoken;
      });
</script>

I can figure out how to get different form, maybe even save it, lost validations and need to redirect somewhere when form is saved.

Any input on this or still the wrong approach ?

Thanks

When trying to debug something like this, it’s usually best to follow the data.

Use your browsers developer tools to see what the view is returning to the browser, and then inspect the DOM to verify that the HTML being rendered is what you expect it to be.

You’ve got a couple typos here as well. I suspect that you’re retyping code rather than copy/pasting the original. (The form_view shown here is syntactically incorrect and would throw an error - it will not run as-is. You also have a typo for csrfToken at document.querySelector("#csrfTokken").value=csrftoken;)

yes freehand typed nut match the same structure as the original.
no typo, the forms.html have the hidden input id=“csrfTokken”, cant use the {% csrf_token %} tag as this page never hit the browser only being used to render the form, the page interact with the browser test.html don’t have element so I loading the {% csrf_token %} tag in test.html, get its value and set the input named “csrfTokken” (was explained in the document this way Cross Site Request Forgery protection | Django documentation | Django)
csrfTokken is a typo but works as that’s just a element id.

<form action="/forms/" method="POST" name="form">
     <input id="csrfTokken" type="hidden" name="csrfmiddlewaretoken" value=""/>
        {{ form|crispy}}
     <button class="btn btn-primary btn-outline my-3" type="submit">Save</button>
</form>

Sure you can. You’re rendering the template on the server - it doesn’t matter how it ends up being rendered in the browser.

Anyway, this all is a side-issue. The real key is that you need to see what is being rendered, what you’re sending to the browser, and how the browser is injecting it into the DOM.

If you need help with the Django view, then you’ll need to copy/paste the actual code - we cannot debug a representation of it.

{"form": "\n\n    <form action=\"/management/forms/\" method=\"POST\" name=\"form\">\n        <input id=\"csrfTokken\" type=\"hidden\" name=\"csrfmiddlewaretoken\" value=\"\"/>\n        \n\n<div id=\"div_id_region\" class=\"form-group\"> <label for=\"id_region\" class=\" requiredField\">\n                Region<span class=\"asteriskField\">*</span> </label> <div> <input type=\"text\" name=\"region\" class=\"form-control textinput textInput\" placeholder=\"Region Short Name /* NYC*/\" maxlength=\"128\" required id=\"id_region\"> </div> </div> <div class=\"form-group\"> <div id=\"div_id_include\" class=\"form-check\"> <input type=\"checkbox\" name=\"include\" class=\"checkboxinput form-check-input\" id=\"id_include\" checked> <label for=\"id_include\" class=\"form-check-label\">\n                    Include\n                </label> </div> </div>\n\n        <button class=\"btn btn-primary btn-outline my-3\" type=\"submit\">Save</button>\n    </form>\n"}

actual test codes:
view–

def test_view(request):
    return render(request, 'management/test.html')


def get_forms(request):
    form = RegionForm(request.POST or None)
    if request.method == 'POST':
        if form.is_valid():
            form.save()
            return redirect('/management/test/')
        else:
            template = render_to_string(
                'management/forms.html',  {"form": form})
            return render(request, 'management/test.html')
            # return JsonResponse({"form": template})
    else:
        template = render_to_string('management/forms.html', {"form": form})
        return JsonResponse({"form": template})

forms.py–

class RegionForm(forms.ModelForm):
			class Meta:
				model = Region
				fields = '__all__'

models.py–

class Region(models.Model):
    region = models.CharField(max_length=128, unique=True)
    include = models.BooleanField(default=True)

    class Meta:
        verbose_name_plural = "Regions"

    def natural_key(self):
        return self.region

    def __str__(self):
        return self.region

urls–

urlpatterns = [
    path("", home_view, name="home"),
    path('forms/', get_forms, name="get_forms"),
    path('test/', test_view, name="test"),
]

test.html–

{% extends "base_generic.html" %} {% load static %} {% block javascript %} {{block.super}}

{% endblock javascript %} {% block content %} {% block css %} {{block.super}}
{% endblock css %}
{% load crispy_forms_tags %}
<div class="row">
    <div class="col-1 mt-2 mx-2 bg-white border" style="width: 160px; background-color: #cfcfcf">
        {% csrf_token %}
        <div class="form-container"></div>
    </div>
</div>

{% endblock content %} {% block script %}
<script>
    const url = "{% url 'get_forms' %}";
    const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value;

    fetch(url)
      .then(function (response) {
        return response.json();
      })
      .then(function (data) {
        const formContainer = document.querySelector(".form-container");
        formContainer.innerHTML = data.form;
        document.querySelector("#csrfTokken").value=csrftoken;
      });
</script>
{% endblock script %}

forms.html–

{% load crispy_forms_tags %}

    <form action="/management/forms/" method="POST" name="form">
        <input id="csrfTokken" type="hidden" name="csrfmiddlewaretoken" value=""/>
        {{ form|crispy}}
        <button class="btn btn-primary btn-outline my-3" type="submit">Save</button>
    </form>

Save works, generally speaking it works, what I cant figure out now is when form.is_valid() False
the generate a new template with the error messages what’s now a json and I have no idea how to pass it back to the test.html

Why not just return it the way it was originally sent? The rendered form should have the error messages rendered in it.

how ? return render(request) need a template, return render(request, ‘management/test.html’) render new form without error messages, return JsonResponse({“form”: template}) return JSON string with error

{"form": "\n\n    <form action=\"/management/forms/\" method=\"POST\" name=\"form\">\n        <input id=\"csrfTokken\" type=\"hidden\" name=\"csrfmiddlewaretoken\" value=\"\"/>\n        \n\n<div id=\"div_id_region\" class=\"form-group\"> <label for=\"id_region\" class=\" requiredField\">\n                Region<span class=\"asteriskField\">*</span> </label> <div> <input type=\"text\" name=\"region\" value=\"NYC\" class=\"form-control textinput textInput is-invalid\" placeholder=\"Region Short Name /* EDM */\" maxlength=\"128\" required id=\"id_region\"> <p id=\"error_1_id_region\" class=\"invalid-feedback\"><strong>Region with this Region already exists.</strong></p> </div> </div> <div class=\"form-group\"> <div id=\"div_id_include\" class=\"form-check\"> <input type=\"checkbox\" name=\"include\" class=\"checkboxinput form-check-input\" id=\"id_include\" checked> <label for=\"id_include\" class=\"form-check-label\">\n                    Include\n                </label> </div> </div>\n\n        <button class=\"btn btn-outline-primary my-3\" type=\"submit\">Save</button>\n    </form>\n"}

Isn’t that what you want?

When you render the original form, you’re returning it as a blank form as JSON:

So, if the form is submitted with errors, don’t you want those errors returned to the user?

(I guess I’m not seeing what it is you want to have happen.)

yes I do want the error message that’s why asked how, the page is test.html, and the error message is in forms.html what’s only get into test.html as JSON from the fetch, when page submitted to get_forms to have it validated and save when there is an error IDK how to send it back to test.html

I’m sorry, I can’t figure out what you’re trying to say here.

Taking a step back to fundamental principles:

  • When you first render a form, it is “unbound”. There is no data associated with it.

  • When you post the form data to the server, the form gets bound to the data being submitted.

  • When you call is_valid, or any of a number of other functions, the submitted data is validated, and any errors are attached to the form.

  • When you render that validated form, the error messages are rendered with the form.

  • You have the ability to alter any of these steps, either directly in the view or by creating a custom form class that you subclass that alters this default behavior.

But it all starts with understanding what it is that you want to be working with at each step of the process.

In the browser, in this particular case, you are issuing a GET to get the basic form.

You add the form to the current page.

The submit button is pressed - but you probably aren’t looking at submitting the entire page, just this current form. (Common for an SPA-style application) This means you want to handle the Submit event to submit the form via AJAX.

The response from the AJAX call is up to you. It could be the same form with the errors displayed, or it could be a different form, or it could be something else - that’s up to you.

It might be worth your while to review the Working with forms page, especially the section on the view to where you understand what every line of code is doing in the example.

Then, keep in mind that there’s no fundamental difference between a view that returns a full html page, a view that returns an HTML fragment, or a view that returns JSON.

A view (any view) receives a request and returns a response. It’s up to you to decide what that response needs to be and how it’s to be handled by the browser.

  • When you first render a form, it is “unbound”. There is no data associated with it. -correct
  • When you post the form data to the server, the form gets bound to the data being submitted. - correct
  • When you call is_valid, or any of a number of other functions, the submitted data is validated, and any errors are attached to the form. - yes but the form is in the forms.html and the form rendered in the test.html page
  • When you render that validated form, the error messages are rendered with the form. -yes but it returned as template from render_to_string(‘management/forms.html’, {“form”: form})
  • You have the ability to alter any of these steps, either directly in the view or by creating a custom form class that you subclass that alters this default behavior.

Can I setup a github repo with a example ?

Because you told it to:

But you’re not doing anything with that. You’re “throwing it away”.

You create a variable named template, but you return something completely different and ignore the fact that you created this variable.

So again I need to ask - what is it that you’re trying to accomplish here?

When an error is detected in a submitted form, what do you want to have happen?

yes, not using it as now a JSON string what just rendered in the browser as pain text

<p id=\"error_1_id_region\" class=\"invalid-feedback\"><strong>Region with this Region already exists.</strong>

there is something fundamentally wrong with my approach or i just don’t see the tree from the forest

Ken, thanks for your patients and contribution, Im really grateful.

Lest just roll back, would it be just easier to create all the views and paths for all the forms ?
If you would have 50 model would you create individual views, links or programmatically find a way.
Now that Im typing and thinking of the admin page, django admin create all the proper forms with add, edit, delete to any model programmatically, how is that done ?