Django and Dropzone.js

12,561

Solution 1

I'm working with Dropzone and Django myself for creating Image objects for each file uploaded, which seems to be akin to what you want to do. I'd like to point out some things that I've experienced and show you how I'm doing it to see if that helps.

What you need

The things that you need in order to create a record in the Database for files uploaded with Dropzone is:

  1. The Dropzone HTML form
  2. The Javascript initialization of Dropzone.
  3. A Django View to handle the uploaded files.

I don't understand what you're doing with the Form (is it just validating?) but it seems to be unnecessary. You don't need it (and don't use it) to actually save the file.

Accessing the uploaded files

First lets talk about how to access the files in request.FILES. By setting uploadMultiple: true on your Dropzone configuration you condition Dropzone not to send dzfile but to send each file represented as dzfile[%d] (i.e. dzfile[0], dzfile[1], etc).

Even if that was not the case you're using request.FILES like if it was a list (for f in request.FILES), but like you point out it's actually a dict.

Here's what Python shows when I print request.FILES:

<MultiValueDict: {u'dzfile[1]': [<InMemoryUploadedFile: image2.jpg (image/jpeg)>], u'dzfile[2]': [<InMemoryUploadedFile: image3.jpg (image/jpeg)>], u'dzfile[0]': [<InMemoryUploadedFile: image1.jpg (image/jpeg)>]}>

To access the actual files you need to get each key by it's name.

files = [request.FILES.get('dzfile[%d]' % i)
     for i in range(0, len(request.FILES))]

NOW you have the file list you wanted. Simply iterate through it and create your objects however you want. I'm not sure on how your Models work so I'm going to approximate.

for f in files:
    # Create a ClientUpload object by setting its FK to client and
    # FileField to the file. Correct me if I deduced the models incorrectly
    client_upload = ClientUpload.objects.create(
        client=current_client,
        file_upload=f,
    )

That should be enough to create the objects that you want.

Dropzone Javascript

It seems that in the Click event listener you add to the submit button you have to add

e.preventDefault();
e.stopPropagation();

before calling processQueue() to avoid a double form submission.

As to the sendingmultiple, successmultiple and errormultiple, what do you want to happen there? The comments are just there to indicate when those events are trigered.

I personally use:

this.on('sendingmultiple', function () {
    // `sendingmultiple` to hide the submit button
    $('#my-dropzone').find('button[type=submit]').hide();
});
this.on('successmultiple', function (files, response) {
    // `successmultiple` to reload the page (and show the updated info)
    window.location.reload();
});
this.on('errormultiple', function (files, response) {
    // `errormultiple` to un-hide the button
    $('#my-dropzone').find('button[type=submit]').show();
});

But of course you can do what you want.

And finally, what do you intend to happen with that last line in the <script> tag? I don't quite understand it, it looks like if you wanted to re-process the queue on success. It seems not to belong there.

Comment if anything's off, but this setup works fine for me.

Solution 2

In your Javascript, I think you want to use this for the paramName:

paramName: "file_upload",

in order for a Django Form or ModelForm to recognize the uploaded files.

Also make sure that the upload request is using a multipart/form-data content type.

Also, try this instead of "get_list":

dz_files = request.FILES.getlist("file_upload")
Share:
12,561

Related videos on Youtube

ss7
Author by

ss7

Updated on June 04, 2022

Comments

  • ss7
    ss7 almost 2 years

    When I upload files with dropzone it adds them to the database, but they don't have a file, just an ID and creation date. I think the view is the problem but I've tried tons of stuff and I can't figure it out. See my edit below for a more detailed account.

    Here is the view

    @login_required(login_url='/dashboard-login/')
    def dashboard(request):
        current_user = request.user
        current_client = request.user.client
    
        files = ClientUpload.objects.filter(client=current_client)
    
        form = UploadFileForm()
    
        if request.method == 'POST':
            if request.FILES is None:
                logger = logging.getLogger(__name__)
                logger.warning("No files were attached to the upload.")
                return HttpResponseBadRequest('No Files Attached.')
    
            if form.is_valid():
                upload = form.save()
                form = UploadFileForm(request.POST, request.FILES)
    
            else:
                uploaded_files = [request.FILES.get('file_upload[%d]' % i)
                    for i in range(0, len(request.FILES))]
    
                for f in uploaded_files:
                    client_upload = ClientUpload.objects.create(client=current_client, file_upload=f)
    
                #for key in request.FILES:
                #    cupload = ClientUpload.objects.create(client=current_client, file_upload=request.FILES[key])
    
            logger = logging.getLogger(__name__)
            logger.debug(request.FILES)
            logger.info("File(s) uploaded from " + current_client.company)          
    
            return HttpResponseRedirect(reverse('dashboard'))
    
        data = {'form': form, 'client': current_client, 'files': files}
        return render_to_response('dashboard.html', data, context_instance=RequestContext(request))
    

    Here are my dz options:

    url: '127.0.0.1:8003/dashboard/',
          method: "post",
          withCredentials: false,
          parallelUploads: 12,
          uploadMultiple: true,
          maxFilesize: 256*4*2,
          paramName: "file_upload",
          createImageThumbnails: true,
          maxThumbnailFilesize: 20,
          thumbnailWidth: 100,
          thumbnailHeight: 100,
          maxFiles: 12,
          params: {},
          clickable: true,
          ignoreHiddenFiles: true,
          acceptedFiles: null,
          acceptedMimeTypes: null,
          autoProcessQueue: false,
          addRemoveLinks: true,
          previewsContainer: null,
          dictDefaultMessage: "Drop files here to upload",
          dictFallbackMessage: "Your browser does not support drag and drop file uploads.",
          dictFallbackText: "Please use the fallback form below to upload your files.",
          dictFileTooBig: "File is too big ({{filesize}}MB). Max filesize: {{maxFilesize}}MB.",
          dictInvalidFileType: "You can't upload files of this type.",
          dictResponseError: "Server responded with {{statusCode}} code.",
          dictCancelUpload: "Cancel upload",
          dictCancelUploadConfirmation: "Are you sure you want to cancel this upload?",
          dictRemoveFile: "Remove",
          dictRemoveFileConfirmation: null,
          dictMaxFilesExceeded: "You can only upload {{maxFiles}} files.",
    

    And here is the template:

    {% load i18n %}
    {% load staticfiles %}
    {% load crispy_forms_tags %}
    
    <link href="{% static 'css/dropzone2.css' %}" type="text/css" rel="stylesheet"/>
    
    <form class="dropzone" id="myDropzone" method="post" action="{% url 'dashboard' %}" enctype="multipart/form-data">
        {% csrf_token %}
        <div class="fallback">
            <input name="file" type="file" multiple />
        </div>  
    </form>
    <button class="upload-control btn-success btn" type="submit" id='submit-all' onclick="document.getElementById('myDropzone').submit()">
        <i class="glyphicon glyphicon-upload"></i>
        <span>{% trans 'Submit' %}</span>
    </button>
    
    <style>
        .upload-control {
            margin-top: 10px;
            margin-bottom: 0px;
        }
    </style>
    <script src="{% static 'js/dropzone.js' %}"></script>
    <script src="{% static 'js/jquery-2.1.4.min.js' %}"></script>
    <script type="text/javascript">
        Dropzone.autoDiscover = false
        $(document).ready(function() {
            Dropzone.options.myDropzone = {
    
                init : function() {
                    var submitButton = document.querySelector("#submit-all")
                    myDropzone = this;
    
                    submitButton.addEventListener("click", function(e) {
                        e.stopPropagation();
                        e.preventDefault();
                        myDropzone.processQueue();
                    });
    
                    this.on("sendingmultiple", function() {
                        // Figure out what I want here or if I want at all
                    });
    
                    this.on("successmultiple", function(files, response) {
                        window.location.reload();
                    });
    
                    this.on("errormultiple", function(files, response) {
                        // Figure out what I want here or if I want at all
                    });
    
                    }
                    // Do I need this?
                    //myDropzone.on('success', myDropzone.processQueue.bind(myDropzone));
            };
    });    
    </script>
    

    EDIT:

    It works now after adding http:// to the url setting. But when I upload a file it is added to the database, but the file field is blank. The multivaluedict shows the file when I print it out, but when it is saved to the database the file field has nothing in it.

    When I upload one file I get this in request.FILES:

    <MultiValueDict: {u'file_upload[]': [<InMemoryUploadedFile: normal.PNG (image/png)>]}>

    When I upload two I get this in request.FILES:

    <MultiValueDict: {u'file_upload[]': [<TemporaryUploadedFile: normal.PNG (image/png)>]}>

    Despite being two files it only shows the one, but adds them both to the database (both without files and just ID and creation date). Also what is TemporaryUploadedFile and InMemoryUploadedFile?

    It should have indexes in the u'file_upload[]' when I upload more than one but it doesn't. I have the settings correct for uploading multiples.

    But I can't seem to get them out of the MultiValueDict. And when I try something like:

    for upload in request.FILES:
        client_upload = ClientUpload.objects.create(client=current_client, file_upload=upload)
    

    I run into that problem where the admin panel shows an ID and time but no file. It happens when uploading one or more. I'm not sure what the difference is between InMemoryUploadedfile and TemporaryUploadedFile either. How can I extract the files from the MultiValueDict? get() is not working, with the list comp I just get an empty list.

    The other odd thing, is when I upload certain files the MultiValueDict is empty, and with others it is not. Also it seems that my view gets called more than once (according to the log outputs) and that is normal, except it should be a post then redirect to a get, but it seems to have more than one post request. I checked the dev tools in chrome and I only see one, but oddly it outputs my log statement twice for every time I submit. I know the issue is probably in my view but I've tried a ton of stuff and can't figure out what is wrong.

    Anybody have any ideas?

  • ss7
    ss7 almost 9 years
    Gave it a try, but it keeps failing the form.is_valid() check
  • ss7
    ss7 almost 9 years
    I am getting a POST response though when I remove the check, and it does do the 302 redirect, but the getlist('file_upload)' returns [] I assume i could fix the form.is_valid() failing by overriding the form methods, but I still haven't a clue as to why I keep getting an empty list. I am getting the error "dropzone already attached" though. Not sure if that is relevant.
  • ss7
    ss7 almost 9 years
    This seems perfect. I think with this and a small configuration fix it will work. As you said the form is pretty much useless. I will give this a shot
  • Seán Hayes
    Seán Hayes over 8 years
    Regardless of whether the form is working or not, request.FILES should have something in there if the file is being sent to the server correctly. That's where we need to start.
  • Seán Hayes
    Seán Hayes over 8 years
    Check this out to see if you can get rid of the "dropzone already attached" error: github.com/enyo/dropzone/wiki/…
  • Agustín Lado
    Agustín Lado over 8 years
    The approach is sound, I can guarantee it works for me. Surely you'll get it working, but if there's a problem it's likely a tiny quirk, so good luck.
  • ss7
    ss7 over 8 years
    When I print out request.FILES into alog I get this: (DEBUG) request.FILES: <MultiValueDict: {}> . But I need to figure out why form validation fails or how to not need it since as you said the form doesn't do much. But I know request.FILES is not none since it passes that check. I'm unsure of what to do. Why wouldn't it capture the files into that dict? I'm sure it will work if the dict was not empty. And as form the form.is_valid() I am thoroughly confued.
  • Agustín Lado
    Agustín Lado over 8 years
    When a form fails to validate you can see the errors in form.errors. Maybe check that out. I can't help you out much more but I'll give you this tip: open the debug panel in Chrome or Firefox with Ctrl+Shift+i, go to the network section and look for the file upload request. If the files are being sent correctly the problem lies with the form (which you should remove, a simple View suffices. Try seeing what's up by debugging with import pdb; pdb.set_trace()). Else there's something wrong with the Dropzone code (it shouldn't be very hard to solve the issue; use the browser's devtools).
  • ss7
    ss7 over 8 years
    I can tell the problem is that request.FILES is empty. I have removed the form.
  • ss7
    ss7 over 8 years
    If you can help me figure the rest out I'll award another 100 bounty.