Send the data with a POST request with an inlineformset through ajax.

Hello everyone, I have a problem when I want to send the data of a form and an inlineformset through ajax. The truth is I don’t know which is the correct way to do it and I hope to find it here, thanks in advance. In principle I have two forms in a createview (ModelForm and inlineformset), the ModelForm refers to the data of a Provider and the inlineformset to its Contacts. I will leave the code below.
Using ajax in this way throws me the error: SyntaxError: Unexpected token < in JSON at position 1

# models.py
class Proveedor(models.Model):
    tipoDoc = models.CharField(
        max_length=30, verbose_name='Documentación')
    numDoc = models.CharField(
        max_length=20, unique=True, verbose_name='Número')
    razon_social = models.CharField(max_length=50, verbose_name='Razón Social')
    personeria = models.CharField(
        max_length=40, choices=PERSONERIA, default='F', verbose_name='Personería')
    cat_impositiva = models.CharField(
        max_length=50, choices=CATEGORIA_IMPOSITIVA, default='RI', verbose_name='Categoria Impositiva')

    def __str__(self):
        return self.razon_social

    class Meta:
        verbose_name = 'Proveedor'
        verbose_name_plural = 'Proveedores'
        ordering = ['id']


class Contacto(models.Model):
    nombre = models.CharField(max_length=50, verbose_name='Nombre')
    email = models.EmailField(
        max_length=30, blank=True, null=True, verbose_name='Email')
    telefono = models.CharField(
        max_length=30, blank=True, null=True, verbose_name='Teléfono')
    proveedor = models.ForeignKey(
        Proveedor, on_delete=models.CASCADE)

    def __str__(self):
        return self.nombre

    class Meta:
        verbose_name = 'Contacto'
        verbose_name_plural = 'Contactos'
        ordering = ['id']
#forms.py
class ProveedorForm(ModelForm):

    class Meta:
        model = Proveedor
        fields = '__all__'


class ContactoForm(ModelForm):

    class Meta:
        model = Contacto
        fields = '__all__'
      
    def save(self, commit=True):
        data = {}
        form = super()
        try:
            if form.is_valid():
                form.save()
            else:
                data['error'] = form.errors
        except Exception as e:
            data['error'] = str(e)
        return data

ContactoFormSet = inlineformset_factory(
    Proveedor, Contacto, form=ContactoForm, extra=1, max_num=10)
# views.py
class ProveedorCreate(LoginRequiredMixin, CreateView):
    model = Proveedor
    form_class = ProveedorForm
    template_name = 'proveedor/create.html'
    success_url = reverse_lazy('app_compra:proveedor_list')

    def get_context_data(self, **kwargs):
        data = super().get_context_data(**kwargs)
        data['list_url'] = reverse_lazy('app_compra:proveedor_list')
        data['action'] = 'add'
        if self.request.POST:
            data['contacto_formset'] = ContactoFormSet(self.request.POST)
        else:
            data['contacto_formset'] = ContactoFormSet()
        return data

    def form_valid(self, form):
        context = self.get_context_data()
        contacto = context['contacto_formset']
        with transaction.atomic():
            self.object = form.save()
            if contacto.is_valid():
                contacto.instance = self.object
                contacto.save()
        return super(ProveedorCreate, self).form_valid(form)
<!-- create.html -->
{% extends 'body.html' %}
{% load widget_tweaks %}
{% load static %}

{% block content %}
    <form method="post" action="." class='container-fluid' enctype="multipart/form-data">
            <div class="card-body">
                {% csrf_token %}
                <input type="hidden" name="action" value="{{ action }}">
                
                 {{ form.tipoDoc }}
                   
                 {{ form.numDoc }}
                  
                 {{ form.razon_social }}
                     
                 {{ form.personeria }}
                     
                 {{ form.cat_impositiva }}
                      
              <table class="table">
                  {{ contacto_formset.management_form }}
              
                  {% for form in contacto_formset.forms %}
                      {% if forloop.first %}
                      <thead>
                          <tr>
                              {% for field in form.visible_fields %}
                                  <th>{{ field.label|capfirst }}</th>
                              {% endfor %}
                          </tr>
                      </thead>
                      {% endif %}
                      <tr class="formset_row">
                          {% for field in form.visible_fields %}
                              <td>
                                  {# Include the hidden fields in the form #}
                                  {% if forloop.first %}
                                      {% for hidden in form.hidden_fields %}
                                          {{ hidden }}
                                      {% endfor %}
                                  {% endif %}
                                  {{ field.errors.as_ul }}
                                  {{ field }}
                              </td>
                          {% endfor %}
                      </tr>
                  {% endfor %}
              </table>

            </div>
            <div class="card-footer">
                <button type="submit" class="btn btn-primary" id='btnAdd'>
                    <i class="fas fa-save"></i> Guardar registro
                </button>
                <a href="{{ list_url }}" class="btn btn-danger">
                    <i class="fas fa-times"></i> Cancelar
                </a>
            </div>
        </div>
    </form>
{% endblock %}

{% block script %}
<!-- SweetAlert2 -->
<script src="{% static 'libs/sweetalert2-11.3.10/sweetalert2.min.js' %}"></script>
<!-- jQueryConfirm -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-confirm/3.3.2/jquery-confirm.min.js"></script>

<script>
function submit_with_ajax(url, title, content, parameters, callback) {
    $.confirm({
        theme: 'material',
        title: title,
        icon: 'fa fa-info',
        content: content,
        columnClass: 'small',
        typeAnimated: true,
        cancelButtonClass: 'btn-primary',
        draggable: true,
        dragWindowBorder: false,
        buttons: {
            info: {
                text: "Si",
                btnClass: 'btn-primary',
                action: function () {
                    $.ajax({
                        url: url,
                        type: 'POST',
                        data: parameters,
                        dataType: 'json',
                        processData: false,
                        contentType: false,
                    }).done(function (data) {
                        console.log(data);
                        if (!data.hasOwnProperty('error')) {
                            callback(data);
                            return false;
                        }
                        
                    }).fail(function (jqXHR, textStatus, errorThrown) {
                        alert(textStatus + ': ' + errorThrown);
                    }).always(function (data) {

                    });
                }
            },
            danger: {
                text: "No",
                btnClass: 'btn-red',
                action: function () {

                }
            },
        }
    })
}
    $('form').on('submit', function (e) {
        e.preventDefault();
        var parameters = new FormData(this);
        submit_with_ajax(window.location.pathname, 'Notificación', '¿Estas seguro de realizar la siguiente acción?', parameters, function () {
            location.href = '{{ list_url }}';
        });
    });

</script>

{% endblock script %}

It’s worth mentioning that without ajax it works fine, but without ajax I can’t use jQueryConfirm (I think so).

you should use return JsonResponse in Django !

Please provide more information about the error. Is this an error that you are receiving in the browser, or in the view? If in the view, please provide the complete traceback from the error.

Also, you can look at your browser’s developer tools on the network tab to see what you’re sending as the request. You should visually verify that what you’re sending is what you’re expecting to send.

Finally, do you have this technique working on other pages? If not, then you might want to consider creating a simpler page as a test case, and once you’ve got it working, start adding things from there.

I receive the error in the browser, I visually verified what is being sent and it is correct.

**FormData**  // This is a test
csrfmiddlewaretoken: 7u6jnNSrqRzOnDCDdiHPDhM1UKZpjHKt9EaTci0NNBNXs4rBczempEn4wBAVlyOm
action: add
tipoDoc: DNI
numDoc: 123456
razon_social: Facundo Rodriguez
personeria: F
cat_impositiva: RI
contacto_set-TOTAL_FORMS: 3
contacto_set-INITIAL_FORMS: 0
contacto_set-MIN_NUM_FORMS: 0
contacto_set-MAX_NUM_FORMS: 10
contacto_set-0-proveedor: 
contacto_set-0-id: 
contacto_set-0-nombre: Martin
contacto_set-0-email: martin@gmail.com
contacto_set-0-telefono: 654987987
contacto_set-0-DELETE: 
contacto_set-1-proveedor: 
contacto_set-1-id: 
contacto_set-1-nombre: Facundo
contacto_set-1-email: facundo@gmail.com
contacto_set-1-telefono: 987987

What confuses me is that the record is still successfully uploaded to the database when I close the alert. But I don’t know the error appears.

Lastly, no, I don’t have this technique working on other pages. It only works fine if I don’t send the data via ajax.

That’s not JSON you’re sending. That’s HTML form data.
It’s ok to work with that using AJAX, just ensure that none of your JavaScript code is expecting to work with JSON.

For example:

probably isn’t right.

I removed it but it keeps throwing me the error.

Should you remove it, or replace it with a more appropriate value? (I don’t know - I’m not familiar with the JavaScript libraries you are using.) Likewise, how else are you handling the response? It appears to me that you would be getting back an HttpResponse - what’s this code doing with that?
I don’t have answers to those questions - but that’s the areas I believe you should be investigating. Something in your code is trying to treat data as JSON when it’s not.

I started with a simpler form and then added the inlineforset and came up with a solution at least for this case. I eliminated the form_valid method that I had modified and I modified the post method being this way:

    def post(self, request, *args, **kwargs):
        data = {}
        try:
            action = request.POST['action']
            if action == 'add':
                form = self.get_form()
                contacto = ContactoFormSet(request.POST)
                if form.is_valid() and contacto.is_valid():
                    with transaction.atomic():
                        self.object = form.save()
                        contacto.instance = self.object
                        contacto.save()
                elif not form.is_valid():
                    data['error'] = form.errors
                elif not contacto.is_valid():
                    data['error'] = contacto.errors
            else:
                data['error'] = 'No existe la acción'
        except Exception as e:
            data['error'] = str(e)
        return JsonResponse(data)

Since you’re effectively handling the complete response, I wouldn’t use a CBV for this. It’s going to be a lot simpler overall if you just had this as an FBV to handle the submission.