[Django]-Bootstrap Modal Forms Posting Twice Django

7👍

Update to this post: It seems like that the package was updated and the original issue was solved, thus links in this answer won’t point you to the correct codes/comments anymore. If you are still experiencing the same problem, try upgrading the package first.


After reading the source code for the package, I believe that receiving two requests on the backend is normal.

Your code

$(document).ready(function() {

    $(".create-shipment").modalForm({
        formURL: "{% url 'CreateShipmentView' %}"
    });

});

triggers the function modalForm where it takes your options (formURL) and assign the function newForm to a click event.

Then in the newForm function it calls the function addListeners to bind a click event to the submit button in the modal, and the event is called as following:

isFormValid(modalID, modalContent, modalForm, formURL, errorClass, submitBtn, submitForm);

Note that the last parameter submitForm points to the following function

var submitForm = function(modalForm) {
      $(modalForm).submit();
    };

Finally in the function isFormValid, all the data you entered in the form will be posted to the url you defined in the action attribute for validation, and if there is no error, the form will be submitted to the very same url.

If you dig into the python code in this package, things become interesting. The BSModalForm is based on two classes in mixins.py and in here it says that when the request was made by anything other than ajax, save the instance created using the form data, or otherwise (if requested by ajax call) don’t save the instance and return it. That’s why it validated the form at first but should never save it (remember the first call was indeed originated by using the ajax call in jQuery).

You mentioned that the form was being saved twice – try to add a line at the beginning of the save function as

print(request.is_ajax())

and then check the output. it could be that the call failed to be sent as an AJAX call. (if that’s the case, update your jquery version or use something else to make the call like axios)

If you don’t like how things are happening, few options (package under MIT license):

  1. Change the save function to validate the instance then save it as a normal django save function, but that involves changing some of the JS code.

  2. Make an API endpoint to receive the data and communicate using json instead of returning html code every time (I guess this is also the reason why the author wrote the JS in the current way because you then face the rendering issue). Since currently you don’t need to perform any other action after submitting the form, it doesn’t make sense to return the instance anymore. (no need for DRF as there is a JsonResponse class built-in in django and if you only need this one endpoint)

  3. Use BootStrap directly since the story here is rather simple: a modal on the page, a button to trigger the modal, a form in the modal and you can submit it. You might need to write some of your own JS for error displaying but it should still be easier than changing the existing package.


An example

# forms.py
from django import forms


# use django ModelForm for creating the form based on model
class CreateShipmentForm(forms.ModelForm):
    class Meta:
        model = Shipment
        fields = ['Reference_Number', 'Ultimate_Consignee']

view for rendering the form (GET) and receiving form submissions (POST)

# views.py
from yourapp.forms import CreateShipmentForm
from django.shortcuts import render


def create_shipment_view(request):
    # http POST request means that the form was submitted
    if request.method == 'POST':
        # create the form and map the data to CreateShipmentForm
        shipment_form = CreateShipmentForm(request.POST)
        # based on your design of the form/modal, validate the data passed into the form
        if shipment_form.is_valid():
            # create a new shipment object
            new_shipment = shipment_form.save()
            # form processing is finished now do something to alert user, for example redirect the user to the success page
            return redirect('success')
        # if the form is not valid, then shipment_form.errors contain all the errors
    else:
        # for all other accessing methods including GET
        # create an empty form
        shipment_form = CreateShipmentForm()
    
    # if the request was POST, the form contains errors, if request was GET, it's an empty form
    return render(request, 'path/to/template.html', {
        'form': shipment_form
    })

Finally in your template, you can just display the form as you would normally. If you don’t know or need some other features for your form, keep using crispy forms.

check if shipment_form.errors in your template and display it in your template if you want to show errors to the users.

👤Foocli

3👍

I had similar problem. My solution was:

class AppCreateView(BSModalCreateView):
    template_name = 'apps/app_create.html'
    form_class = UserAppForm
    success_message = 'Success: App was created.'
    success_url = reverse_lazy('dashboard')

    def form_valid(self, form):
        if not self.request.is_ajax():
            app = form.save(commit=False)
            profile = Profile.objects.get(user=self.request.user)
            app.profile = profile
            app.save()
        return HttpResponseRedirect(self.success_url)
👤alex

Leave a comment