Search code examples
ajaxdjangofile-uploaddjango-2.0

Django: Uploading files with AJAX: Form says that the file input field is empty (or CSRF token missing or incorrect)


I'm trying to add a very simple file upload modal form in my Django app.

But, when I click the submit button, the form shows me an error message: "this field is required".

Everything renders correctly:

  • My main page loads correctly
  • When I click in the "Agregar archivo adjunto" button ("add attachment"), the modal form shows correctly, and all the fields are rendered as I want them to.
  • The issue comes when I click on the "Adjuntar archivo" ("attach file") button in my modal field: The form throws an error, as if I was trying to upload a "null" file!

Now... I forgot to add cache: false, contentType: false, processData: false to the $.ajax() call, but, when I add them, I get the following error: Forbidden (CSRF token missing or incorrect.). So... I don't know how to proceed!

I've already wrote (successfully) a modal form which helps me add notes (related) to my lead object (using this reference), and I'm trying to reproduce exactly the same process for a file upload modal dialog... but it doesn't work :(

Any help will be really appreciated.

By the way: I'm testing this using Chrome (no IE will be used!)



Here is my code:

models.py

def lead_dir_path(instance, filename):
    """
    Files will be saved to: MEDIA_ROOT/leads/<int:pk>/<filename>
    where <int:pk> is lead's primary key, and <filename> is just that.

    Filename will be set to an UUID value.
    """
    ext = filename.split('.')[-1]
    filename = '%s.%s' % (uuid.uuid4(), ext)
    return 'leads/%s/%s' % (instance.lead.pk, filename)


class ArchivosAdjuntosLead(models.Model):
    lead = models.ForeignKey(Lead, on_delete=models.CASCADE)
    descripcion = models.CharField(max_length=100)
    archivo = models.FileField(upload_to=lead_dir_path)

views.py

def agregar_adjunto_json(request, pk):
    """
    Adds a file to lead with id=pk
    """
    context = {}
    data = {}

    lead = get_object_or_404(Lead, pk=pk)
    context['lead'] = lead

    if request.method == 'POST':
        form = AdjuntarArchivoLeadForm_v2(request.POST, request.FILES)
        if form.is_valid():
            form.save();
            data['form_is_valid'] = True
        else:
            data['form_is_valid'] = False
    else:
        form = AdjuntarArchivoLeadForm_v2()
        form.initial = {'lead': lead}
    context['form'] = form

    data['html_form'] = render_to_string(
        template_folder + 'partial_templates/partial_adjuntar_archivo.html',
        context,
        request = request,
    )
    return JsonResponse(data)

forms.py

class AdjuntarArchivoLeadForm_v2(forms.ModelForm):
    class Meta():
        model = ArchivosAdjuntosLead
        fields = ['lead', 'descripcion', 'archivo']

        widgets = {
          'lead': forms.TextInput(attrs={'class':'form-control', 'style':'display:none;'}),
          'descripcion': forms.TextInput(attrs={'class':'form-control'}),
          'archivo': forms.FileInput(attrs={'class':'form-control'}),
        }

partial_adjuntar_archivo.html

I use this partial template to create a modal form:

<form method="POST" enctype="multipart/form-data"
      action="{% url 'leads:agregar_adjunto_v2' pk=lead.pk %}"
      id="js_adjuntar_archivo_form">
  {% csrf_token %}

  <div class="modal-header">
    <h4 class="modal-title">Adjuntar archivo</h4>
  </div>

  <div class="modal-body">

    {{ form.as_p }}

    <div class="modal-footer">
      <button type="submit" class="btn btn-primary col-4">Adjuntar archivo</button>
      <button type="button" class="btn btn-secondary col-4" data-dismiss="modal">Cancelar</button>
    </div>
  </div>
</form>

my_lead_page.html

This is the page where I create the modal form:

{% extends "leads/base.html" %}

{% load static %}

{% block contenido %}

<!-- Lots and lots of info -->
<button type="button" class="btn btn-sm btn-primary col-2" id="btn_agregar_adjunto">
  Agregar archivo adjunto
</button>
{% endblock %}

{% block other_scripts %}
<script type="text/javascript" src="{% static 'js/leads/archivos_adjuntos.js'%}"></script>

{% endblock %}

archivos_adjuntos.js

$(function() {
  $("#btn_agregar_adjunto").click(function() {
    $.ajax({
      url: 'adjuntar_archivo/',
      type: 'get',
      dataType: 'json',
      beforeSend: function() {
        $("#modal-form").modal("show");
      },
      success: function(data) {
        $("#modal-form .modal-content").html(data.html_form);
      }
    });
  });

  $("#modal-form").on("submit", "#js_adjuntar_archivo_form", function() {
    var form = $(this);
    $.ajax({
      url: form.attr("action"),
      data: form.serialize(),
      type: form.attr("method"),
      dataType: 'json',
      cache: false,
      contentType: false,
      processData: false, 
      success: function(data) {
        if(data.form_is_valid) {
          alert("Archivo adjuntado");
        } else {
          $("#modal-form .modal-content").html(data.html_form);
        }
      }
    });
    return false;
  });
  
});

Solution

  • Instead of form.serialize() try sending it with js formData() it should work.

    Here is an example:

    $("#modal-form").on("submit", "#js_adjuntar_archivo_form", function() {
        $that = this;
        var form = new FormData($(this)[0]);
    
        $.ajax({
            url:$that.attr("action"),
            type:$that.attr("method"),
            data:form,
            processData: false,
            contentType: false,
    
            // rest of the code'''
    
        });
        return false;
    });