helloflask / flask-dropzone

Upload files in Flask application with Dropzone.js.
https://flask-dropzone.readthedocs.io
MIT License
250 stars 69 forks source link

Nesting dropzone in existing form? #14

Open clpatterson opened 6 years ago

clpatterson commented 6 years ago

I have a form for posting blog entries. I want to be able to submit multiple fields at once. Instead of designating the class of the form as 'dropzone' is it possible to be able to have a dropzone div nested in my current form so one button will submit all my fields, including multiple image upload, at once? Something like this stackoverflow question, but with flask-dropzone. Thanx in advance for any assistance!

My form:


<!DOCTYPE HTML>
<form action="{{ url_for('add_post') }}" method="post" class="add-post" enctype="multipart/form-data">
   <dl>
      <dt>Post title:
      <dd><input type="text" size="30" name="title" spellcheck="true" required>
      <dt>Post date:
      <dd><input type="date" name="post_date" required>
      <dt>Post description(one sentence):
      <dd><textarea name="description" rows="3" cols="40" spellcheck="true" required></textarea>
      <dt>Post html file:
      <dd><input type="file" name="html_file" required>
      <dt>Post image(s) file:</dt>

      <!--Can something like this be done?-->
      <div class="dropzone" id="myDropzone">
         {{ dropzone.create(action=url_for('show_posts')) }}
      </div>

      <dd><input type="submit" value="Submit">
   </dl>
</form>
greyli commented 6 years ago

Thanks, this is a very helpful feature, I will try to implement it this week.

greyli commented 6 years ago

It seems that there isn't a perfect way to handle this problem. As far as I know, the following thing is easy:

But the problem is, Dropzone.js use AJAX to send files, so we can't merge the files with the normal POST request that created when you click the submit button. The most of the methods in the SO question you posted is append the form data into the AJAX request that created by Dropzone.js, but then it will be hard to get the error messages from server-side when validation fails.

greyli commented 6 years ago

An alternative method is to send uploads and other form data to different view functions, for example:

However, I can't make the form submit after file upload complete:

    document.getElementById("submit").addEventListener("click", function(e) {
         // prevent submit form first
        e.preventDefault();
        e.stopPropagation();
        myDropzone.processQueue();  // upload files
        document.getElementById("submit").click();  // submit other fields --> not work
    });

Any idea?

greyli commented 6 years ago

I finally make the alternative method worked, you can try the demo application in here.

gyrcom commented 6 years ago

Trying the demo. Get errors BuildError: Could not build url for endpoint ''. Did you mean 'handle_upload' instead? It seems the url_for command is not getting the config setting for DROPZONE_UPLOAD_ACTION

greyli commented 6 years ago

@gyrcom Thanks for the feedback! It should be work with the code on master branch, you can install it on local with:

$ git clone https://github.com/greyli/flask-dropzone.git
$ cd flask-dropzone
$ pip install -e .

then run the demo with:

$ python examples/in-form/app.py
gyrcom commented 6 years ago

Thank You

gabrielggg commented 6 years ago

i tried the code and received this output from server when submitting the form

Traceback (most recent call last): File "/usr/local/lib/python3.5/dist-packages/flask/app.py", line 1997, in call return self.wsgi_app(environ, start_response) File "/usr/local/lib/python3.5/dist-packages/flask/app.py", line 1985, in wsgi_app response = self.handle_exception(e) File "/usr/local/lib/python3.5/dist-packages/flask/app.py", line 1540, in handle_exception reraise(exc_type, exc_value, tb) File "/usr/local/lib/python3.5/dist-packages/flask/_compat.py", line 33, in reraise raise value File "/usr/local/lib/python3.5/dist-packages/flask/app.py", line 1982, in wsgi_app response = self.full_dispatch_request() File "/usr/local/lib/python3.5/dist-packages/flask/app.py", line 1614, in full_dispatch_request rv = self.handle_user_exception(e) File "/usr/local/lib/python3.5/dist-packages/flask/app.py", line 1517, in handle_user_exception reraise(exc_type, exc_value, tb) File "/usr/local/lib/python3.5/dist-packages/flask/_compat.py", line 33, in reraise raise value File "/usr/local/lib/python3.5/dist-packages/flask/app.py", line 1612, in full_dispatch_request rv = self.dispatch_request() File "/usr/local/lib/python3.5/dist-packages/flask/app.py", line 1598, in dispatch_request return self.view_functionsrule.endpoint File "/home/deeplearning/gabriel/front/front/test/flask-dropzone/examples/in-form/app.py", line 33, in handle_upload for key, f in request.files.iteritems(): AttributeError: 'ImmutableMultiDict' object has no attribute 'iteritems'

wasnt able to upload files

i solved it putting items() instead of iteritems() in python3

greyli commented 6 years ago

@gabrielggg Yes, you are right, I forgot to add a comment to address this.

Permafacture commented 6 years ago

Both python 2 and 3 have a dict.items. There's really no memory or performance benefit to not just using items in both python 2 and 3.

Permafacture commented 6 years ago

Hmm, sticky. Assuming we need the files and form data in one function in order to create a database record or something, I can think of three options:

1) Don't use AJAX for the dropzone component when it is part of a larger form, and lose the nice dropzone validation feedback. Instead embed the file upload validation feedback in the template like the rest of the form elements.

2) Merge the form data into the AJAX, and allow the user to specify a callback function which get the response (after dropzone has used it) and can update the form elements.

3) Do two requests as you are currently doing and include a common uuid in both requests. The file handling function can use the key to put the files somewhere where the form data handling function can retrieve them.

#1 clears the dropzone ui so server side per file validation messages can't be shown the way dropzone usually does. (is this a big deal?) . #2 could be fine but would compel users to figure out how to display field and form validation messages through javascript rather than through the templates like WTForms. This might even merit a library of it's own, since adding other filed types is probably out of scope for flask-dropzone. #3 is trivial after the work that has already been done. It is limited in that form validation cannot influence file validation but I can't really think of a solid example of this being an issue (clearing the dropzone files in the UI on form level validation failure seems fine in all situations I can think of).

greyli commented 6 years ago

Both python 2 and 3 have a dict.items. There's really no memory or performance benefit to not just using items in both python 2 and 3.

Technically, use dict.iteritems() did can improve performance since it did not create a new list. However, use dict.items() in an example application is enough and it will be more friendly for users use different Python version. I will update the example application and README later this day (You can also make a PR if you want).

As for nesting form issue, thanks for your thoughful analysis. I actually trid to implement #1 and #2, but both are diffcult to implement or hard to use. #3 looks good, I'm happy to review the PR if you can implement it.

greyli commented 6 years ago

items() issue fixed in https://github.com/greyli/flask-dropzone/commit/f30a61d1913a92abeec0485f4012072d5ee35569.

Permafacture commented 6 years ago

I've created a pull request addressing #3 and partially #2. #3 really just needed the example updated, using the csrf token, though if you are fine with using a real CSRF token in that example then I'd add hashing the token rather than using the token to make the path directly. Completing #2 would require an example of updating the init function with a callback. I might do that if we end up needing that functionality for our project.

ps. not sure how link the the PR. haven't done this on github before

pps. iteritems doesn't make a list but it does make a generator object, so it's still constructing a new python object. It's not free. items builds the list in C code which is really fast. Especially for dictionaries with only dozens of elements, it's not clear to me which would be faster, and both would be so fast that doing anything with each element would dwarf the time it takes to make the list or generator. And network io would be orders of magnitude more. So, I disagree that iteritems improves performance here.

greyli commented 6 years ago

#2 is not a good idea since user will need a normal POST request to handle form validation then render the page with error messages.

You can just type # with the number to link an issue or PR.

I agree, it is almost no different for small dict.

harry-wright commented 6 years ago

My take was similiar to @Permafacture

I was considering generating a random UUID.

The images would upload instantly to a '/temp/' folder using handle_images_uploads().

The form would submit with UUID to handle_form() and check if there were any images added to the folder.

If my understanding's correct though - I don't seem to be able to send the CSRF token twice. So it adds vulnerability to the app.

harry-wright commented 6 years ago

I'm also not sure if anyone's seen this link

greyli commented 6 years ago

@harry-wright The current implementation was just inspired by the link you posted.

gyrcom commented 5 years ago

Back to this again, the examples work very well, I am trying however, to get in-form working with csrf. If not possible, I will just implement a popup. Thx

gyrcom commented 5 years ago

I've spent a fair lot of time trying lots of options. In-form works well until I try to get csrf working. I do not have the necessary javascript skills to debug this. Has anyone gotten in-form and csrf working together?

gyrcom commented 5 years ago

Well I've spent some more time on this. The problem stems from not being able to use the csrf token more than once. My work around has been to start a javascript popup window to implement the dropzone, and when finished, use queuecomplete to then close the popup. I do not think there is any easy answer to integrating dropzone, forms, and csrf.

harry-wright commented 5 years ago

Hello Greyli I too spent a lot of time researching this

I have not researched it but the answer seemed to lie in a multi-part form. I.e. can we use FormData.append() in Flask-wtf? It may be possible to submit it with the multi-part form (containings strings and images) and submit it all using ONE csrf token.

Sorry I can't be of more help.

Kind regards

On Mon, 10 Jun 2019 at 01:39, gyrcom notifications@github.com wrote:

Well I've spent some more time on this. The problem stems from not being able to use the csrf token more than once. My work around has been to start a javascript popup window to implement the dropzone, and when finished, use queuecomplete to then close the popup. I do not think there is any easy answer to integrating dropzone, forms, and csrf.

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/greyli/flask-dropzone/issues/14?email_source=notifications&email_token=AEH6CQZN432XJOQ4DQ535FLPZWPEPA5CNFSM4FHCIXO2YY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGODXIVQIQ#issuecomment-500258850, or mute the thread https://github.com/notifications/unsubscribe-auth/AEH6CQ3IDMNCAIASZB5IOH3PZWPEPANCNFSM4FHCIXOQ .

greyli commented 5 years ago

@harry-wright Thanks for the tip, I'll have a look at this when I have free time.

itsDrac commented 3 years ago

Hi @greyli I've been looking for the answer for this issue but i can not find any thing that would be of any help. Besides that, your commit from 7 day ago does seem to provide an alternative solution However it would be great conveniences if this issue get resolves...

Thanks.