'How to upload multiple files in Django using Dropzone js?

Info: I want to upload multiple files using Dropzone js in Django project. I have two models. One for the Post and the other would be for the File. My files model would have a foreignkey to the Post model. About my Views.py when the user is filling out the form post he has to complete the Files form too for the post.

Fine: When i submit the Ajax_file_uploads form instead of using Dropzone.js multiple selected files are attached with single Post instance.

Problem: If i try to upload multiple files using Dropzone.js Multiple articles are created along with multiple files when I submit the form.

Any help would be much appreciated!

models.py

class Post(models.Model):
    title = models.CharField(max_length=100, blank=True)
    content = models.TextField(blank=True)

class FileAttach(models.Model):
    file = models.FileField(upload_to='uploads/')
    post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='file_attach', null=True)

views.py

def Ajax_file_uploads(request):
    if request.method == "POST":
        p_form = PostForm(request.POST or None)
        form = AttachForm(request.POST or None, request.FILES)

        if p_form.is_valid():
            art = p_form.save(commit=False)
            art.user = request.user
            art.save()
            files = request.FILES.getlist('file')
            if form.is_valid():
                for f in files:
                    file_instance = FileAttach(file=f, post=art)
                    file_instance.save()

            return JsonResponse({"error": False, "message": "Uploaded Successfully"})
        else:
            return JsonResponse({"error": True, "errors": p_form.errors})
    else:
        p_form = PostForm()
        form = AttachForm()
        return render(request, "upload/api.html", {"p_form": p_form, "form": form})

create.html

<script>
    Dropzone.autoDiscover = false;
    // Dropzone.options.myDropzone = false;

    // set the dropzone container id
    var id = "#kt_dropzonejs_example_2";
    // set the preview element template
    var previewNode = $(id + " .dropzone-item");
    previewNode.id = "";
    var previewTemplate = previewNode.parent(".dropzone-items").html();
    previewNode.remove();

    var myDropzone = new Dropzone(id, { // Make the whole body a dropzone
        // url: "/uploader/files/", // Set the url for your upload script location
        url: '/uploader/multi/',
        headers: { 'X-CSRFToken': '{{ csrf_token }}' },
        timeout: 2000000,
        parallelUploads: 100,
        previewTemplate: previewTemplate,
        // uploadMultiple: true,
        maxFilesize: 200, // Max filesize in MB
        maxFiles: 5, // Max filesize in MB
        autoQueue: false, // Make sure the files aren't queued until manually added
        previewsContainer: id + " .dropzone-items", // Define the container to display the previews
        clickable: id + " .dropzone-select", // Define the element that should be used as click trigger to select files.
        uploadprogress: function (file, progress, bytesSent) {
            if (file.previewElement) {
                var progressElement = file.previewElement.querySelector("[data-dz-uploadprogress]");
                progressElement.style.width = progress + "%";
                progressElement.querySelector(".dropzone-progress-total").textContent = progress.toFixed(0) + "%";
            }
        },
        sending: function (file, xhr, formData) {
            // Show the total progress bar when upload starts
            $(id + " .progress-bar").css("opacity", "1");
            // Add other form data
            var data = $('#form-data').serializeArray();
            $.each(data, function (key, el) {
                formData.append(el.name, el.value);
            });
        },
        // success: function (file) {
        //     file.previewElement.remove()
        // }
        init: function () {
            $("#form-data").validate({
                submitHandler: function (form) {
                    myDropzone.enqueueFiles(myDropzone.getFilesWithStatus(Dropzone.ADDED));
                }
            });
        },
    });

    myDropzone.on("addedfile", function (file) {
        // Hookup the start button
        $(document).find(id + " .dropzone-item").css("display", "");
        $(id + " .dropzone-remove-all").css("display", "inline-block");
    });

    // Hide the total progress bar when nothing's uploading anymore
    myDropzone.on("complete", function (progress) {
        var thisProgressBar = id + " .dz-complete";
        setTimeout(function () {
            $(thisProgressBar + " .progress-bar, " + thisProgressBar + " .progress").css("opacity", "0");
        }, 300)
    });

    // Setup the button for remove all files
    document.querySelector(id + " .dropzone-remove-all").onclick = function () {
        $(id + " .dropzone-remove-all").css("display", "none");
        myDropzone.removeAllFiles(true);
    };

    // On all files removed
    myDropzone.on("removedfile", function (file) {
        if (myDropzone.files.length < 1) {
            $(id + " .dropzone-remove-all").css("display", "none");
        }
    });
</script>


Solution 1:[1]

Dropzone submits each file separately via AJAX calls and when it does that it submits the file and all form inputs with each AJAX call. Based on the way your views.py file is written, this will cause a Post instance to be created and then a FileAttach instance to be created and associated with the Post instance.

I see three ways you could fix this:

  1. Modify your views.py file to check for an existing Post instance before creating a new one. Because each file is uploaded asynchronously there is still a chance that the first Post instance would not be created before the second file upload looks for it and thus two Post instances would still be created.
  2. Add some additional JavaScript to first make an AJAX call that creates the Post instance and returns its id value then allow Dropzone to make its AJAX calls that include the Post.id value. This answer outlines the opposite approach (upload files, then submit form); the concept just needs to be reversed.
  3. Set the uploadMultiple option to true on the Dropzone object so that all the files (and the form) are submitted in one submission to the backend. I have not worked with this so not sure how you would handle that in views.py. My guess is that request.FILES would contain multiple file entries, but again I am not sure how your AttachForm would deal with that.

If it were me, I would go with option 2.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 PaulR