'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:
- 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. - 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 thePost.id
value. This answer outlines the opposite approach (upload files, then submit form); the concept just needs to be reversed. - Set the
uploadMultiple
option totrue
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 inviews.py
. My guess is thatrequest.FILES
would contain multiple file entries, but again I am not sure how yourAttachForm
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 |