├── examples ├── basic │ ├── uploads │ │ └── .gitkeep │ ├── templates │ │ └── index.html │ └── app.py ├── csrf │ ├── uploads │ │ └── .gitkeep │ ├── templates │ │ └── index.html │ └── app.py ├── in-form │ ├── uploads │ │ └── .gitkeep │ ├── templates │ │ └── index.html │ └── app.py ├── click-upload │ ├── uploads │ │ └── .gitkeep │ ├── templates │ │ └── index.html │ └── app.py ├── large-file │ ├── uploads │ │ └── .gitkeep │ ├── templates │ │ └── index.html │ └── app.py ├── complete-redirect │ ├── uploads │ │ └── .gitkeep │ ├── templates │ │ └── index.html │ └── app.py ├── custom-options │ ├── uploads │ │ └── .gitkeep │ ├── templates │ │ └── index.html │ └── app.py ├── multiple-dropzone │ ├── uploads │ │ └── .gitkeep │ ├── templates │ │ └── index.html │ └── app.py ├── parallel-upload │ ├── uploads │ │ └── .gitkeep │ ├── templates │ │ └── index.html │ └── app.py ├── requirements.txt └── README.rst ├── docs ├── changelog.rst ├── examples.rst ├── _static │ ├── flask-dropzone.png │ └── flask-dropzone-small.png ├── _templates │ ├── sidebarlogo.html │ └── sidebarintro.html ├── _themes │ ├── flask │ │ ├── theme.conf │ │ ├── relations.html │ │ ├── layout.html │ │ └── static │ │ │ └── flasky.css_t │ ├── flask_small │ │ ├── theme.conf │ │ ├── layout.html │ │ └── static │ │ │ └── flasky.css_t │ ├── README │ ├── LICENSE │ └── flask_theme_support.py ├── api.rst ├── Makefile ├── make.bat ├── index.rst ├── basic.rst ├── advanced.rst ├── conf.py └── configuration.rst ├── docs_requirements.txt ├── MANIFEST.in ├── test_requirements.txt ├── resources └── validation.png ├── setup.cfg ├── tox.ini ├── flask_dropzone ├── utils.py ├── static │ └── dropzone.min.css └── __init__.py ├── README.md ├── LICENSE.txt ├── setup.py ├── .github └── workflows │ └── tests.yaml ├── README.rst ├── .gitignore ├── CHANGES.rst └── test_flask_dropzone.py /examples/basic/uploads/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /examples/csrf/uploads/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /examples/in-form/uploads/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /examples/click-upload/uploads/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /examples/large-file/uploads/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /examples/complete-redirect/uploads/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /examples/custom-options/uploads/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /examples/multiple-dropzone/uploads/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /examples/parallel-upload/uploads/.gitkeep: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/changelog.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../CHANGES.rst 2 | -------------------------------------------------------------------------------- /docs_requirements.txt: -------------------------------------------------------------------------------- 1 | flask-sphinx-themes 2 | . 3 | -------------------------------------------------------------------------------- /docs/examples.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../examples/README.rst 2 | -------------------------------------------------------------------------------- /examples/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask 2 | Flask-Dropzone 3 | Flask-WTF -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | graft flask_dropzone/static 2 | include *.txt, *.md, *.rst 3 | -------------------------------------------------------------------------------- /test_requirements.txt: -------------------------------------------------------------------------------- 1 | coverage 2 | flake8 3 | flask-wtf 4 | setuptools 5 | -------------------------------------------------------------------------------- /resources/validation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helloflask/flask-dropzone/main/resources/validation.png -------------------------------------------------------------------------------- /docs/_static/flask-dropzone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helloflask/flask-dropzone/main/docs/_static/flask-dropzone.png -------------------------------------------------------------------------------- /docs/_static/flask-dropzone-small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/helloflask/flask-dropzone/main/docs/_static/flask-dropzone-small.png -------------------------------------------------------------------------------- /docs/_templates/sidebarlogo.html: -------------------------------------------------------------------------------- 1 | 5 | -------------------------------------------------------------------------------- /docs/_themes/flask/theme.conf: -------------------------------------------------------------------------------- 1 | [theme] 2 | inherit = basic 3 | stylesheet = flasky.css 4 | pygments_style = flask_theme_support.FlaskyStyle 5 | 6 | [options] 7 | index_logo = '' 8 | index_logo_height = 120px 9 | touch_icon = 10 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | license_file = LICENSE.txt 3 | 4 | [bdist_wheel] 5 | universal = 1 6 | 7 | [coverage:run] 8 | source = flask_dropzone 9 | branch = true 10 | 11 | [flake8] 12 | exclude = static, build, docs 13 | max-line-length = 119 14 | -------------------------------------------------------------------------------- /docs/_themes/flask_small/theme.conf: -------------------------------------------------------------------------------- 1 | [theme] 2 | inherit = basic 3 | stylesheet = flasky.css 4 | nosidebar = true 5 | pygments_style = flask_theme_support.FlaskyStyle 6 | 7 | [options] 8 | index_logo = '' 9 | index_logo_height = 120px 10 | github_fork = '' 11 | -------------------------------------------------------------------------------- /docs/api.rst: -------------------------------------------------------------------------------- 1 | API Reference 2 | ============== 3 | 4 | Dropzone Object in Template 5 | ---------------------------- 6 | .. module:: flask_dropzone 7 | 8 | .. autoclass:: _Dropzone 9 | :members: 10 | :undoc-members: 11 | 12 | Utils 13 | ----- 14 | 15 | .. module:: flask_dropzone.utils 16 | 17 | .. autofunction:: get_url 18 | .. autofunction:: random_filename 19 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py37, py38, py39, py310, py311, py312, lint 3 | skip_missing_interpreters = true 4 | skipsdist = true 5 | 6 | [testenv] 7 | deps = 8 | -r test_requirements.txt 9 | commands = 10 | coverage run --source=flask_dropzone setup.py test 11 | coverage report 12 | 13 | [testenv:lint] 14 | deps = 15 | flake8 16 | commands = 17 | flake8 flask_dropzone test_flask_dropzone.py 18 | -------------------------------------------------------------------------------- /examples/csrf/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Flask-Dropzone Demo: CSRF 6 | {{ dropzone.load_css() }} 7 | {{ dropzone.style('border: 2px dashed #0087F7; margin: 10%; min-height: 400px;') }} 8 | 9 | 10 | {{ dropzone.create('upload') }} 11 | {{ dropzone.load_js() }} 12 | {{ dropzone.config() }} 13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/large-file/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Flask-Dropzone Demo: Large File 6 | {{ dropzone.load_css() }} 7 | {{ dropzone.style('border: 2px dashed #0087F7; margin: 10%; min-height: 400px;') }} 8 | 9 | 10 | {{ dropzone.create(action='upload') }} 11 | {{ dropzone.load_js() }} 12 | {{ dropzone.config() }} 13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/complete-redirect/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Flask-Dropzone Demo: Complete Redirect 6 | {{ dropzone.load_css() }} 7 | {{ dropzone.style('border: 2px dashed #0087F7; margin: 10%; min-height: 400px;') }} 8 | 9 | 10 | {{ dropzone.create('upload') }} 11 | {{ dropzone.load_js() }} 12 | {{ dropzone.config() }} 13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/parallel-upload/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Flask-Dropzone Demo: Parallel Upload 6 | {{ dropzone.load_css() }} 7 | {{ dropzone.style('border: 2px dashed #0087F7; margin: 10%; min-height: 400px;') }} 8 | 9 | 10 | {{ dropzone.create(action='upload') }} 11 | {{ dropzone.load_js() }} 12 | {{ dropzone.config() }} 13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/click-upload/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Flask-Dropzone Demo: Click Upload 6 | {{ dropzone.load_css() }} 7 | {{ dropzone.style('border: 2px dashed #0087F7; margin: 10px 0 10px; min-height: 400px;') }} 8 | 9 | 10 | {{ dropzone.create('/') }} 11 | 12 | 13 | {{ dropzone.load_js() }} 14 | {{ dropzone.config() }} 15 | 16 | 17 | -------------------------------------------------------------------------------- /docs/_templates/sidebarintro.html: -------------------------------------------------------------------------------- 1 |

About

2 |

3 | Upload files in Flask application with Dropzone.js. 4 |

5 |

Useful Links

6 | 13 | -------------------------------------------------------------------------------- /examples/basic/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Flask-Dropzone Demo: Basic 6 | {{ dropzone.load_css() }} 7 | {{ dropzone.style('border: 2px dashed #0087F7; margin: 10%; min-height: 400px;') }} 8 | 9 | 10 | {{ dropzone.create(action='upload') }} 11 | {{ dropzone.load_js() }} 12 | {{ dropzone.config() }} 13 | {# You can get the success response from server like this: #} 14 | {#{ dropzone.config(custom_options="success: function(file, response){console.log(response);}") }#} 15 | 16 | 17 | -------------------------------------------------------------------------------- /flask_dropzone/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | import uuid 3 | 4 | from flask import url_for 5 | 6 | 7 | def get_url(endpoint_or_url, **kwargs): 8 | if endpoint_or_url == '': 9 | return 10 | if endpoint_or_url.startswith(('https://', 'http://', '/')): 11 | return endpoint_or_url 12 | else: 13 | return url_for(endpoint_or_url, **kwargs) 14 | 15 | 16 | # generate a random filename, replacement for werkzeug.secure_filename 17 | def random_filename(old_filename): 18 | ext = os.path.splitext(old_filename)[1] 19 | new_filename = uuid.uuid4().hex + ext 20 | return new_filename 21 | -------------------------------------------------------------------------------- /examples/multiple-dropzone/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Flask-Dropzone Demo: Multiple Dropzone 6 | {{ dropzone.load_css() }} 7 | {{ dropzone.style('border: 2px dashed #0087F7; margin: 10%; min-height: 400px;') }} 8 | 9 | 10 | {{ dropzone.create(action='upload', id='foo') }} 11 | {{ dropzone.create(action='upload', id='bar') }} 12 | {{ dropzone.load_js() }} 13 | {{ dropzone.config(id='foo', default_message='Dropzone Foo') }} 14 | {{ dropzone.config(id='bar', default_message='Dropzone Bar') }} 15 | 16 | 17 | -------------------------------------------------------------------------------- /examples/README.rst: -------------------------------------------------------------------------------- 1 | Try Examples 2 | ============= 3 | 4 | Open a terminal, type the commands below one by one:: 5 | 6 | $ git clone https://github.com/helloflask/flask-dropzone 7 | $ cd flask-dropzone/examples 8 | $ pip install -r requirements.txt 9 | $ python basic/app.py 10 | 11 | Then go to http://127.0.0.1:5000 with your favourite browser. 12 | 13 | Aside from the basic example, there are a couple of additional examples: 14 | 15 | - examples/click-upload 16 | - examples/complete-redirect 17 | - examples/csrf 18 | - examples/custom-options 19 | - examples/in-form 20 | - examples/large-file 21 | - examples/parallel-upload 22 | - examples/multiple-dropzone 23 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = Flask-Dropzone 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | %: Makefile 20 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /docs/_themes/flask/relations.html: -------------------------------------------------------------------------------- 1 |

Related Topics

2 | 20 | -------------------------------------------------------------------------------- /examples/custom-options/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Flask-Dropzone Demo: Custom Options 6 | {{ dropzone.load_css() }} 7 | {{ dropzone.style('border: 2px dashed #0087F7; margin: 10%; min-height: 400px;') }} 8 | 9 | 10 | {{ dropzone.create(action='upload') }} 11 | 12 | {{ dropzone.load_js() }} 13 | {{ dropzone.config(custom_init='dz = this;document.getElementById("upload-btn").addEventListener("click", function handler(e) {dz.processQueue();});', 14 | custom_options='autoProcessQueue: false, addRemoveLinks: true, parallelUploads: 20,') }} 15 | 16 | 17 | -------------------------------------------------------------------------------- /docs/_themes/flask_small/layout.html: -------------------------------------------------------------------------------- 1 | {% extends "basic/layout.html" %} 2 | {% block header %} 3 | {{ super() }} 4 | {% if pagename == 'index' %} 5 |
6 | {% endif %} 7 | {% endblock %} 8 | {% block footer %} 9 | {% if pagename == 'index' %} 10 |
11 | {% endif %} 12 | {% endblock %} 13 | {# do not display relbars #} 14 | {% block relbar1 %}{% endblock %} 15 | {% block relbar2 %} 16 | {% if theme_github_fork %} 17 | Fork me on GitHub 19 | {% endif %} 20 | {% endblock %} 21 | {% block sidebar1 %}{% endblock %} 22 | {% block sidebar2 %}{% endblock %} 23 | -------------------------------------------------------------------------------- /docs/_themes/flask/layout.html: -------------------------------------------------------------------------------- 1 | {%- extends "basic/layout.html" %} 2 | {%- block extrahead %} 3 | {{ super() }} 4 | {% if theme_touch_icon %} 5 | 6 | {% endif %} 7 | 8 | {% endblock %} 9 | {%- block relbar2 %}{% endblock %} 10 | {% block header %} 11 | {{ super() }} 12 | {% if pagename == 'index' %} 13 |
14 | {% endif %} 15 | {% endblock %} 16 | {%- block footer %} 17 | 21 | {% if pagename == 'index' %} 22 |
23 | {% endif %} 24 | {%- endblock %} 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Flask-Dropzone 2 | 3 | [![GitHub Actions Build Status](https://github.com/helloflask/flask-dropzone/actions/workflows/tests.yaml/badge.svg)](https://github.com/helloflask/flask-dropzone/actions) 4 | [![PyPI](https://img.shields.io/pypi/v/Flask-Dropzone)](https://pypi.org/project/Flask-Dropzone/) 5 | 6 | Upload files in Flask application with [Dropzone.js](http://www.dropzonejs.com/). 7 | 8 | NOTICE: This extension is built for simple usage, if you need more flexibility, please use Dropzone.js directly. 9 | 10 | ## Links 11 | 12 | * [Documentation](https://flask-dropzone.readthedocs.io/en/latest/) 13 | * [PyPI](https://pypi.org/project/Flask-Dropzone/) 14 | * [Examples](https://github.com/helloflask/flask-dropzone/tree/master/examples) 15 | 16 | ## License 17 | 18 | This project is licensed under the MIT License (see the `LICENSE` file for details). 19 | -------------------------------------------------------------------------------- /examples/in-form/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Flask-Dropzone Demo: In Form 6 | {{ dropzone.load_css() }} 7 | {{ dropzone.style('border: 2px dashed #0087F7; margin: 10px 0 10px; min-height: 400px; width: 800px') }} 8 | 9 | 10 |

New Album

11 |
12 | 13 |
14 | 15 |
16 | {{ dropzone.create() }} 17 | 18 |
19 | {{ dropzone.load_js() }} 20 | {{ dropzone.config() }} 21 | 22 | 23 | -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | set SPHINXPROJ=Flask-Dropzone 13 | 14 | if "%1" == "" goto help 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 20 | echo.installed, then set the SPHINXBUILD environment variable to point 21 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 22 | echo.may add the Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% 34 | 35 | :end 36 | popd 37 | -------------------------------------------------------------------------------- /examples/custom-options/app.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | :author: Grey Li 4 | :copyright: (c) 2017 by Grey Li. 5 | :license: MIT, see LICENSE for more details. 6 | """ 7 | import os 8 | 9 | from flask import Flask, render_template, request 10 | from flask_dropzone import Dropzone 11 | 12 | basedir = os.path.abspath(os.path.dirname(__file__)) 13 | 14 | app = Flask(__name__) 15 | 16 | app.config.update( 17 | UPLOADED_PATH=os.path.join(basedir, 'uploads'), 18 | # Flask-Dropzone config: 19 | DROPZONE_ALLOWED_FILE_TYPE='image', 20 | DROPZONE_MAX_FILE_SIZE=3, 21 | DROPZONE_MAX_FILES=30, 22 | ) 23 | 24 | dropzone = Dropzone(app) 25 | 26 | 27 | @app.route('/', methods=['POST', 'GET']) 28 | def upload(): 29 | if request.method == 'POST': 30 | f = request.files.get('file') 31 | f.save(os.path.join(app.config['UPLOADED_PATH'], f.filename)) 32 | return render_template('index.html') 33 | 34 | 35 | if __name__ == '__main__': 36 | app.run(debug=True) 37 | -------------------------------------------------------------------------------- /examples/large-file/app.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | :author: Grey Li 4 | :copyright: (c) 2017 by Grey Li. 5 | :license: MIT, see LICENSE for more details. 6 | """ 7 | import os 8 | 9 | from flask import Flask, render_template, request 10 | from flask_dropzone import Dropzone 11 | 12 | basedir = os.path.abspath(os.path.dirname(__file__)) 13 | 14 | app = Flask(__name__) 15 | 16 | app.config.update( 17 | UPLOADED_PATH=os.path.join(basedir, 'uploads'), 18 | # Flask-Dropzone config: 19 | DROPZONE_MAX_FILE_SIZE=1024, # set max size limit to a large number, here is 1024 MB 20 | DROPZONE_TIMEOUT=5 * 60 * 1000 # set upload timeout to a large number, here is 5 minutes 21 | ) 22 | 23 | dropzone = Dropzone(app) 24 | 25 | 26 | @app.route('/', methods=['POST', 'GET']) 27 | def upload(): 28 | if request.method == 'POST': 29 | f = request.files.get('file') 30 | f.save(os.path.join(app.config['UPLOADED_PATH'], f.filename)) 31 | return render_template('index.html') 32 | 33 | 34 | if __name__ == '__main__': 35 | app.run(debug=True) 36 | -------------------------------------------------------------------------------- /examples/click-upload/app.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | :author: Grey Li 4 | :copyright: (c) 2017 by Grey Li. 5 | :license: MIT, see LICENSE for more details. 6 | """ 7 | import os 8 | 9 | from flask import Flask, render_template, request 10 | from flask_dropzone import Dropzone 11 | 12 | basedir = os.path.abspath(os.path.dirname(__file__)) 13 | 14 | 15 | app = Flask(__name__) 16 | 17 | app.config.update( 18 | UPLOADED_PATH=os.path.join(basedir, 'uploads'), 19 | # Flask-Dropzone config: 20 | DROPZONE_ALLOWED_FILE_TYPE='image', 21 | DROPZONE_MAX_FILE_SIZE=3, 22 | DROPZONE_MAX_FILES=20, 23 | DROPZONE_UPLOAD_ON_CLICK=True 24 | ) 25 | 26 | dropzone = Dropzone(app) 27 | 28 | 29 | @app.route('/', methods=['POST', 'GET']) 30 | def upload(): 31 | if request.method == 'POST': 32 | for key, f in request.files.items(): 33 | if key.startswith('file'): 34 | f.save(os.path.join(app.config['UPLOADED_PATH'], f.filename)) 35 | return render_template('index.html') 36 | 37 | 38 | if __name__ == '__main__': 39 | app.run(debug=True) 40 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Grey Li 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import io 2 | from setuptools import setup 3 | 4 | with io.open("README.rst", "rt", encoding="utf8") as f: 5 | long_description = f.read() 6 | 7 | 8 | setup( 9 | name='Flask-Dropzone', 10 | version='2.0.0', 11 | url='https://github.com/helloflask/flask-dropzone', 12 | license='MIT', 13 | author='Grey Li', 14 | author_email='withlihui@gmail.com', 15 | description='Upload files in Flask with Dropzone.js.', 16 | long_description=long_description, 17 | packages=['flask_dropzone'], 18 | zip_safe=False, 19 | include_package_data=True, 20 | platforms='any', 21 | install_requires=[ 22 | 'Flask' 23 | ], 24 | keywords='flask extension development upload', 25 | classifiers=[ 26 | 'Development Status :: 5 - Production/Stable', 27 | 'Environment :: Web Environment', 28 | 'Intended Audience :: Developers', 29 | 'License :: OSI Approved :: MIT License', 30 | 'Programming Language :: Python', 31 | 'Programming Language :: Python :: 3', 32 | 'Topic :: Internet :: WWW/HTTP :: Dynamic Content', 33 | 'Topic :: Software Development :: Libraries :: Python Modules' 34 | ] 35 | ) 36 | -------------------------------------------------------------------------------- /examples/parallel-upload/app.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | :author: Grey Li 4 | :copyright: (c) 2017 by Grey Li. 5 | :license: MIT, see LICENSE for more details. 6 | """ 7 | import os 8 | 9 | from flask import Flask, render_template, request 10 | from flask_dropzone import Dropzone 11 | 12 | basedir = os.path.abspath(os.path.dirname(__file__)) 13 | 14 | 15 | app = Flask(__name__) 16 | 17 | app.config.update( 18 | UPLOADED_PATH=os.path.join(basedir, 'uploads'), 19 | # Flask-Dropzone config: 20 | DROPZONE_ALLOWED_FILE_TYPE='image', 21 | DROPZONE_MAX_FILE_SIZE=3, 22 | DROPZONE_MAX_FILES=30, 23 | DROPZONE_PARALLEL_UPLOADS=3, # set parallel amount 24 | DROPZONE_UPLOAD_MULTIPLE=True, # enable upload multiple 25 | ) 26 | 27 | dropzone = Dropzone(app) 28 | 29 | 30 | @app.route('/', methods=['POST', 'GET']) 31 | def upload(): 32 | if request.method == 'POST': 33 | for key, f in request.files.items(): 34 | if key.startswith('file'): 35 | f.save(os.path.join(app.config['UPLOADED_PATH'], f.filename)) 36 | return render_template('index.html') 37 | 38 | 39 | if __name__ == '__main__': 40 | app.run(debug=True) 41 | -------------------------------------------------------------------------------- /docs/_themes/README: -------------------------------------------------------------------------------- 1 | Flask Sphinx Styles 2 | =================== 3 | 4 | This repository contains sphinx styles for Flask and Flask related 5 | projects. To use this style in your Sphinx documentation, follow 6 | this guide: 7 | 8 | 1. put this folder as _themes into your docs folder. Alternatively 9 | you can also use git submodules to check out the contents there. 10 | 2. add this to your conf.py: 11 | 12 | sys.path.append(os.path.abspath('_themes')) 13 | html_theme_path = ['_themes'] 14 | html_theme = 'flask' 15 | 16 | The following themes exist: 17 | 18 | - 'flask' - the standard flask documentation theme for large 19 | projects 20 | - 'flask_small' - small one-page theme. Intended to be used by 21 | very small addon libraries for flask. 22 | 23 | The following options exist for the flask_small theme: 24 | 25 | [options] 26 | index_logo = '' filename of a picture in _static 27 | to be used as replacement for the 28 | h1 in the index.rst file. 29 | index_logo_height = 120px height of the index logo 30 | github_fork = '' repository name on github for the 31 | "fork me" badge 32 | -------------------------------------------------------------------------------- /examples/complete-redirect/app.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | :author: Grey Li 4 | :copyright: (c) 2017 by Grey Li. 5 | :license: MIT, see LICENSE for more details. 6 | """ 7 | import os 8 | 9 | from flask import Flask, render_template, request 10 | from flask_dropzone import Dropzone 11 | 12 | basedir = os.path.abspath(os.path.dirname(__file__)) 13 | 14 | 15 | app = Flask(__name__) 16 | 17 | app.config.update( 18 | UPLOADED_PATH=os.path.join(basedir, 'uploads'), 19 | # Flask-Dropzone config: 20 | DROPZONE_ALLOWED_FILE_TYPE='image', 21 | DROPZONE_MAX_FILE_SIZE=3, 22 | DROPZONE_MAX_FILES=30, 23 | DROPZONE_REDIRECT_VIEW='completed' # set redirect view 24 | ) 25 | 26 | dropzone = Dropzone(app) 27 | 28 | 29 | @app.route('/', methods=['POST', 'GET']) 30 | def upload(): 31 | if request.method == 'POST': 32 | f = request.files.get('file') 33 | f.save(os.path.join(app.config['UPLOADED_PATH'], f.filename)) 34 | return render_template('index.html') 35 | 36 | 37 | @app.route('/completed') 38 | def completed(): 39 | return '

The Redirected Page

Upload completed.

' 40 | 41 | 42 | if __name__ == '__main__': 43 | app.run(debug=True) 44 | -------------------------------------------------------------------------------- /examples/basic/app.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | :author: Grey Li 4 | :copyright: (c) 2017 by Grey Li. 5 | :license: MIT, see LICENSE for more details. 6 | """ 7 | import os 8 | 9 | from flask import Flask, render_template, request, jsonify 10 | from flask_dropzone import Dropzone 11 | 12 | basedir = os.path.abspath(os.path.dirname(__file__)) 13 | 14 | app = Flask(__name__) 15 | 16 | app.config.update( 17 | UPLOADED_PATH=os.path.join(basedir, 'uploads'), 18 | # Flask-Dropzone config: 19 | DROPZONE_ALLOWED_FILE_TYPE='image', 20 | DROPZONE_MAX_FILE_SIZE=3, 21 | DROPZONE_MAX_FILES=30, 22 | ) 23 | 24 | dropzone = Dropzone(app) 25 | 26 | 27 | @app.route('/', methods=['POST', 'GET']) 28 | def upload(): 29 | if request.method == 'POST': 30 | f = request.files.get('file') 31 | file_path = os.path.join(app.config['UPLOADED_PATH'], f.filename) 32 | f.save(file_path) 33 | # You can return a JSON response then get it on client side: 34 | # (see template index.html for client implementation) 35 | # return jsonify(uploaded_path=file_path) 36 | return render_template('index.html') 37 | 38 | 39 | if __name__ == '__main__': 40 | app.run(debug=True) 41 | -------------------------------------------------------------------------------- /examples/multiple-dropzone/app.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | :author: Grey Li 4 | :copyright: (c) 2017 by Grey Li. 5 | :license: MIT, see LICENSE for more details. 6 | """ 7 | import os 8 | 9 | from flask import Flask, render_template, request, jsonify 10 | from flask_dropzone import Dropzone 11 | 12 | basedir = os.path.abspath(os.path.dirname(__file__)) 13 | 14 | app = Flask(__name__) 15 | 16 | app.config.update( 17 | UPLOADED_PATH=os.path.join(basedir, 'uploads'), 18 | # Flask-Dropzone config: 19 | DROPZONE_ALLOWED_FILE_TYPE='image', 20 | DROPZONE_MAX_FILE_SIZE=3, 21 | DROPZONE_MAX_FILES=30, 22 | ) 23 | 24 | dropzone = Dropzone(app) 25 | 26 | 27 | @app.route('/', methods=['POST', 'GET']) 28 | def upload(): 29 | if request.method == 'POST': 30 | f = request.files.get('file') 31 | file_path = os.path.join(app.config['UPLOADED_PATH'], f.filename) 32 | f.save(file_path) 33 | # You can return a JSON response then get it on client side: 34 | # (see template index.html for client implementation) 35 | # return jsonify(uploaded_path=file_path) 36 | return render_template('index.html') 37 | 38 | 39 | if __name__ == '__main__': 40 | app.run(debug=True) 41 | -------------------------------------------------------------------------------- /.github/workflows/tests.yaml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | on: 3 | push: 4 | branches: 5 | - main 6 | - master 7 | paths-ignore: 8 | - 'docs/**' 9 | - '*.md' 10 | - '*.rst' 11 | pull_request: 12 | branches: 13 | - main 14 | - master 15 | jobs: 16 | tests: 17 | name: ${{ matrix.name }} 18 | runs-on: ubuntu-latest 19 | strategy: 20 | fail-fast: false 21 | matrix: 22 | include: 23 | - {name: '3.12', python: '3.12', tox: 'py312'} 24 | - {name: '3.11', python: '3.11', tox: 'py311'} 25 | - {name: '3.10', python: '3.10', tox: 'py310'} 26 | - {name: '3.9', python: '3.9', tox: py39} 27 | - {name: '3.8', python: '3.8', tox: py38} 28 | - {name: '3.7', python: '3.7', tox: py37} 29 | - {name: 'Lint', python: '3.12', tox: lint} 30 | steps: 31 | - uses: actions/checkout@v3 32 | - uses: actions/setup-python@v4 33 | with: 34 | python-version: ${{ matrix.python }} 35 | cache: 'pip' 36 | cache-dependency-path: '*requirements.txt' 37 | - name: update pip 38 | run: | 39 | pip install -U wheel 40 | pip install -U setuptools 41 | python -m pip install -U pip 42 | - run: pip install tox 43 | - run: tox -e ${{ matrix.tox }} 44 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | Flask-Dropzone 2 | ============== 3 | 4 | Upload files in Flask application with `Dropzone.js `_. 5 | 6 | NOTICE: This extension is built for simple usage, if you need more flexibility, please use Dropzone.js directly. 7 | 8 | Contents 9 | --------- 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | basic 15 | configuration 16 | advanced 17 | examples 18 | 19 | 20 | API Reference 21 | ------------- 22 | 23 | If you are looking for information on a specific function, class or 24 | method, this part of the documentation is for you. 25 | 26 | .. toctree:: 27 | :maxdepth: 2 28 | 29 | api 30 | 31 | 32 | Changelog 33 | ---------- 34 | 35 | .. toctree:: 36 | :maxdepth: 2 37 | 38 | changelog 39 | 40 | 41 | Development 42 | ----------- 43 | 44 | We welcome all kinds of contributions. You can run test like this: 45 | 46 | .. code-block:: bash 47 | 48 | $ python setup.py test 49 | 50 | Authors 51 | ------- 52 | 53 | Maintainers: 54 | 55 | - `Grey Li `_ 56 | - `yuxiaoy `_ 57 | 58 | See also the list of 59 | `contributors `_ on GitHub. 60 | 61 | License 62 | ------- 63 | 64 | This project is licensed under the MIT License (see the 65 | ``LICENSE`` file for details). 66 | -------------------------------------------------------------------------------- /examples/csrf/app.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | :author: Grey Li 4 | :copyright: (c) 2017 by Grey Li. 5 | :license: MIT, see LICENSE for more details. 6 | """ 7 | import os 8 | 9 | from flask import Flask, render_template, request 10 | from flask_dropzone import Dropzone 11 | from flask_wtf.csrf import CSRFProtect, CSRFError 12 | 13 | basedir = os.path.abspath(os.path.dirname(__file__)) 14 | 15 | app = Flask(__name__) 16 | 17 | app.config.update( 18 | SECRET_KEY='dev key', # the secret key used to generate CSRF token 19 | UPLOADED_PATH=os.path.join(basedir, 'uploads'), 20 | # Flask-Dropzone config: 21 | DROPZONE_ALLOWED_FILE_TYPE='image', 22 | DROPZONE_MAX_FILE_SIZE=3, 23 | DROPZONE_MAX_FILES=30, 24 | DROPZONE_ENABLE_CSRF=True # enable CSRF protection 25 | ) 26 | 27 | dropzone = Dropzone(app) 28 | csrf = CSRFProtect(app) # initialize CSRFProtect 29 | 30 | 31 | @app.route('/', methods=['POST', 'GET']) 32 | def upload(): 33 | if request.method == 'POST': 34 | f = request.files.get('file') 35 | f.save(os.path.join(app.config['UPLOADED_PATH'], f.filename)) 36 | return render_template('index.html') 37 | 38 | 39 | # handle CSRF error 40 | @app.errorhandler(CSRFError) 41 | def csrf_error(e): 42 | return e.description, 400 43 | 44 | 45 | if __name__ == '__main__': 46 | app.run(debug=True) 47 | -------------------------------------------------------------------------------- /examples/in-form/app.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | :author: Grey Li 4 | :copyright: (c) 2017 by Grey Li. 5 | :license: MIT, see LICENSE for more details. 6 | """ 7 | import os 8 | 9 | from flask import Flask, render_template, request 10 | from flask_dropzone import Dropzone 11 | 12 | basedir = os.path.abspath(os.path.dirname(__file__)) 13 | 14 | app = Flask(__name__) 15 | 16 | app.config.update( 17 | UPLOADED_PATH=os.path.join(basedir, 'uploads'), 18 | # Flask-Dropzone config: 19 | DROPZONE_ALLOWED_FILE_TYPE='image', 20 | DROPZONE_MAX_FILE_SIZE=3, 21 | DROPZONE_MAX_FILES=30, 22 | DROPZONE_IN_FORM=True, 23 | DROPZONE_UPLOAD_ON_CLICK=True, 24 | DROPZONE_UPLOAD_ACTION='handle_upload', # URL or endpoint 25 | DROPZONE_UPLOAD_BTN_ID='submit', 26 | ) 27 | 28 | dropzone = Dropzone(app) 29 | 30 | 31 | @app.route('/') 32 | def index(): 33 | return render_template('index.html') 34 | 35 | 36 | @app.route('/upload', methods=['POST']) 37 | def handle_upload(): 38 | for key, f in request.files.items(): 39 | if key.startswith('file'): 40 | f.save(os.path.join(app.config['UPLOADED_PATH'], f.filename)) 41 | return '', 204 42 | 43 | 44 | @app.route('/form', methods=['POST']) 45 | def handle_form(): 46 | title = request.form.get('title') 47 | description = request.form.get('description') 48 | return 'file uploaded and form submit
title: %s
description: %s' % (title, description) 49 | 50 | 51 | if __name__ == '__main__': 52 | app.run(debug=True) 53 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | =============== 2 | Flask-Dropzone 3 | =============== 4 | 5 | Flask-Dropzone packages `Dropzone.js 6 | `_ into an extension to add file upload support for Flask. 7 | It can create links to serve Dropzone from a CDN and works with no JavaScript code in your application. 8 | 9 | NOTICE: This extension is built for simple usage, if you need more flexibility, please use Dropzone.js directly. 10 | 11 | Basic Usage 12 | ----------- 13 | 14 | Step 1: Initialize the extension: 15 | 16 | .. code-block:: python 17 | 18 | from flask_dropzone import Dropzone 19 | 20 | dropzone = Dropzone(app) 21 | 22 | 23 | Step 2: In your `` section of your base template add the following code:: 24 | 25 | 26 | {{ dropzone.load_css() }} 27 | 28 | 29 | ... 30 | {{ dropzone.load_js() }} 31 | 32 | 33 | You can assign the version of Dropzone.js through `version` argument, the default value is `5.2.0`. 34 | Step 3: Creating a Drop Zone with `create()`, and configure it with `config()`:: 35 | 36 | {{ dropzone.create(action='the_url_which_handle_uploads') }} 37 | ... 38 | {{ dropzone.config() }} 39 | 40 | Also to edit the action view to yours. 41 | 42 | Beautify Dropzone 43 | ----------------- 44 | 45 | Style it according to your preferences through `style()` method:: 46 | 47 | {{ dropzone.style('border: 2px dashed #0087F7; margin: 10%; min-height: 400px;') }} 48 | 49 | More Detail 50 | ----------- 51 | 52 | Go to `Documentation 53 | `_ , which you can check for more 54 | details. 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear in the root of a volume 35 | .DocumentRevisions-V100 36 | .fseventsd 37 | .Spotlight-V100 38 | .TemporaryItems 39 | .Trashes 40 | .VolumeIcon.icns 41 | 42 | # Directories potentially created on remote AFP share 43 | .AppleDB 44 | .AppleDesktop 45 | Network Trash Folder 46 | Temporary Items 47 | .apdisk 48 | 49 | venv/ 50 | *.pyc 51 | 52 | # Packages 53 | *.egg 54 | *.egg-info 55 | dist 56 | build 57 | eggs 58 | .eggs 59 | parts 60 | bin 61 | var 62 | sdist 63 | develop-eggs 64 | .installed.cfg 65 | lib 66 | lib64 67 | __pycache__ 68 | 69 | # Installer logs 70 | pip-log.txt 71 | 72 | # dev 73 | .idea 74 | .coverage 75 | docs/_build/ 76 | 77 | # uploads 78 | examples/basic/uploads/* 79 | examples/csrf/uploads/* 80 | examples/parallel-upload/uploads/* 81 | examples/complete-redirect/uploads/* 82 | examples/click-upload/uploads/* 83 | examples/in-form/uploads/* 84 | examples/large-file/uploads/* 85 | examples/custom-options/uploads/* 86 | examples/multiple-dropzone/uploads/* 87 | 88 | !examples/basic/uploads/.gitkeep 89 | !examples/csrf/uploads/.gitkeep 90 | !examples/parallel-upload/uploads/.gitkeep 91 | !examples/complete-redirect/uploads/.gitkeep 92 | !examples/click-upload/uploads/.gitkeep 93 | !examples/in-form/uploads/.gitkeep 94 | !examples/large-file/uploads/.gitkeep 95 | !examples/custom-options/uploads/.gitkeep 96 | !examples/multiple-dropzone/uploads/.gitkeep 97 | -------------------------------------------------------------------------------- /docs/_themes/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 by Armin Ronacher. 2 | 3 | Some rights reserved. 4 | 5 | Redistribution and use in source and binary forms of the theme, with or 6 | without modification, are permitted provided that the following conditions 7 | are met: 8 | 9 | * Redistributions of source code must retain the above copyright 10 | notice, this list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above 13 | copyright notice, this list of conditions and the following 14 | disclaimer in the documentation and/or other materials provided 15 | with the distribution. 16 | 17 | * The names of the contributors may not be used to endorse or 18 | promote products derived from this software without specific 19 | prior written permission. 20 | 21 | We kindly ask you to only use these themes in an unmodified manner just 22 | for Flask and Flask-related products, not for unrelated projects. If you 23 | like the visual style and want to use it for your own projects, please 24 | consider making some larger changes to the themes (such as changing 25 | font faces, sizes, colors or margins). 26 | 27 | THIS THEME IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 28 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 29 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 30 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 31 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 32 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 33 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 34 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 35 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 36 | ARISING IN ANY WAY OUT OF THE USE OF THIS THEME, EVEN IF ADVISED OF THE 37 | POSSIBILITY OF SUCH DAMAGE. 38 | -------------------------------------------------------------------------------- /CHANGES.rst: -------------------------------------------------------------------------------- 1 | Changelog 2 | =========== 3 | 4 | 2.0.0 5 | ----- 6 | released date: 2024/2/18 7 | 8 | WARNING: **New major upstream release (backwards incompatible!).** 9 | 10 | * Remove the deprecated ``dropzone.load()`` method. 11 | * Added more options to customize messages. 12 | * Drop Python 2 support. 13 | * Add an ``id`` parameter for ``dropzone.style()`` to support 14 | customize unique styles for multiple dropzones in one page (`#53 `_). 15 | * Upgrade Dropzone.js to 5.9.3 version. 16 | 17 | 1.6.0 18 | ----- 19 | released date: 2021/5/1 20 | 21 | * Add a ``id`` parameter for ``dropzone.create()`` and ``dropzone.config()`` to support 22 | customize element id and putting multiple dropzones in one page. 23 | 24 | 1.5.4 25 | ----- 26 | released date: 2019/8/4 27 | 28 | * Fix CSRF protect bug when in form (`#29 `_). 29 | 30 | 1.5.3 31 | ----- 32 | released date: 2018/8/24 33 | 34 | * Built-in resources behaviour will not based on ``FLASK_ENV``. 35 | * Add support to pass configuration variable directly with ``dropzone.config()``, see documentation for more details. 36 | 37 | 1.5.2 38 | ----- 39 | released date: 2018/8/7 40 | 41 | * Add a proper documentation. 42 | * Fix KeyError Exception if ENV isn't defined. 43 | 44 | 1.5.1 45 | ----- 46 | released date: 2018/7/21 47 | 48 | * Change CDN provider to jsDelivr. 49 | * Built-in resources will be used when ``FLASK_ENV`` set to ``development``. 50 | 51 | 52 | 1.5.0 53 | ----- 54 | released date: 2018/7/20 55 | 56 | * ``action`` in ``dropzone.create()`` can be URL or endpoint, ``action_view`` was deprecated. 57 | * Add support to upload all dropped files when specific button (``name="upload"``) was clicked. 58 | * Add configuration variable ``DROPZONE_UPLOAD_ON_CLICK``, ``DROPZONE_UPLOAD_ACTION``, ``DROPZONE_UPLOAD_BTN_ID``. 59 | * Add configuration variable ``DROPZONE_IN_FORM``, ``DROPZONE_UPLOAD_ACTION`` to support create dropzone inside ``
``. 60 | * Add configuration variable ``DROPZONE_TIMEOUT``. 61 | * Add ``custom_init`` and ``custom_options`` parameters in ``dropzone.config()`` to support pass custom JavaScript. 62 | 63 | 1.4.6 64 | ----- 65 | released date: 2018/6/8 66 | 67 | * Change built-in resource's url path to ``dropzone/static/...`` to prevent conflict with user's static path. 68 | 69 | 1.4.4 70 | ----- 71 | released date: 2018/5/28 72 | 73 | * ``dropzone.load()`` method was deprecated due to inflexible. Now it's divided into three methods: 74 | * Use ``load_css()`` to load css resources. 75 | * Use ``load_js()`` to load js resources. 76 | * Use ``config()`` to configure Dropzone. 77 | * Besides, we recommend user to manage the resources manually. 78 | * Add basic unit tests. 79 | 80 | 1.4.3 81 | ------ 82 | released date: 2018/3/23 83 | 84 | * Add support to use custom resources with ``js_url`` and ``css_url`` param in ``load()``. 85 | * Fix built-in static bug (`#11 `_). 86 | * Use package instead of module. 87 | 88 | 1.4.2 89 | ------ 90 | released date: 2018/2/17 91 | 92 | * Add support to integrate with CSRFProtect (enabled via ``DROPZONE_ENABLE_CSRF`` or ``csrf`` flag in ``dropzone.create()``). 93 | * Fix bug: ``False`` in JavaScript. 94 | * Bump built-in resource's version to 5.2.0 95 | * Add ``action`` argument in ``dropzone.create()``. For example, ``dropzone.create(action=url_for('upload'))``. 96 | 97 | 1.4.1 98 | ------ 99 | 100 | * New configuration options: ``DROPZONE_UPLOAD_MULTIPLE``, ``DROPZONE_PARALLEL_UPLOADS``, ``DROPZONE_REDIRECT_VIEW``. 101 | * Fix local static files bug. 102 | * Add support for automatic redirection when upload was conmplete. 103 | 104 | 1.4 105 | --- 106 | 107 | WARNING: **New major upstream release (backwards incompatible!).** 108 | 109 | * Method ``include_dropzone()`` rename to ``load()``. 110 | * Add a ``create()`` method to create dropzone form. 111 | * Add a ``style()`` method to add style to upload area. 112 | * Use ``action_view`` argument (in ``create()``) to set action url. 113 | * Dropzonejs version increase to 5.1.1. 114 | * PEP8 and bug fix. 115 | 116 | 1.3 117 | --- 118 | * Documentation fix. 119 | 120 | 1.2 121 | --- 122 | * Upload address fix. 123 | * Delete useless code. 124 | 125 | 1.1 126 | --- 127 | * Add more configuration options. 128 | * Support local resource serve. 129 | * Add basic documentation. 130 | 131 | 1.0 132 | --- 133 | * Initial release. 134 | -------------------------------------------------------------------------------- /docs/basic.rst: -------------------------------------------------------------------------------- 1 | Basic Usage 2 | ============= 3 | 4 | Installation 5 | ------------ 6 | 7 | .. code-block:: bash 8 | 9 | $ pip install flask-dropzone 10 | 11 | Initialization 12 | --------------- 13 | 14 | Initialize the extension: 15 | 16 | .. code-block:: python 17 | 18 | from flask_dropzone import Dropzone 19 | 20 | app = Flask(__name__) 21 | dropzone = Dropzone(app) 22 | 23 | This extension also supports the `Flask application factory 24 | pattern `__ 25 | by allowing you to create a Dropzone object and then separately 26 | initialize it for an app: 27 | 28 | .. code-block:: python 29 | 30 | dropzone = Dropzone() 31 | 32 | def create_app(config): 33 | app = Flask(__name__) 34 | ... 35 | dropzone.init_app(app) 36 | ... 37 | return app 38 | 39 | Include Dropzone.js Resources 40 | ------------------------------- 41 | 42 | In addition to manage and load resources by yourself 43 | (recommended), you can also use these methods to load resources: 44 | 45 | .. code-block:: jinja 46 | 47 | 48 | {{ dropzone.load_css() }} 49 | 50 | 51 | ... 52 | {{ dropzone.load_js() }} 53 | 54 | 55 | You can assign the version of Dropzone.js through ``version`` argument, 56 | the default value is ``5.2.0``. And, you can pass ``css_url`` and 57 | ``js_url`` separately to customize resources URL. 58 | 59 | Create a Drop Zone 60 | ------------------- 61 | 62 | Creating a Drop Zone with ``create()`` and use ``config()`` 63 | to make the configuration come into effect: 64 | 65 | .. code-block:: jinja 66 | 67 | 68 | {{ dropzone.create(action='the_url_or_endpoint_which_handle_uploads') }} 69 | ... 70 | {{ dropzone.config() }} 71 | 72 | 73 | Remember to edit the ``action`` to the URL or endpoint which handles the 74 | uploads, for example ``dropzone.create(action='upload_view')`` or 75 | ``dropzone.create(action=url_for('upload_view'))``. 76 | 77 | The default ID of the dropzone form element is `myDropzone`, usually you don't 78 | need to change it. If you have specific need, for example, you want to have multiple 79 | dropzones on one page, you can use the ``id`` parameter to assign the id: 80 | 81 | .. code-block:: jinja 82 | 83 | 84 | {{ dropzone.create(id='foo') }} 85 | {{ dropzone.create(id='bar') }} 86 | ... 87 | {{ dropzone.config(id='foo') }} 88 | {{ dropzone.config(id='bar') }} 89 | 90 | 91 | Notice that the same id must passed both in ``dropzone.create()`` and ``dropzone.config()``. 92 | 93 | Beautify Dropzone 94 | ----------------- 95 | 96 | Style it according to your preferences through ``dropzone.style()`` method: 97 | 98 | .. code-block:: jinja 99 | 100 | 101 | {{ dropzone.load_css() }} 102 | {{ dropzone.style('border: 2px dashed #0087F7; margin: 10%; min-height: 400px;') }} 103 | 104 | 105 | Notice that you could use manual `` 118 | 119 | 120 | This would apply CSS code to all the dropzones on the page. If you have specific need, for example, 121 | you want to have unique styles for multiple dropzones on one page, you can use the ``id`` parameter to 122 | assign the id: 123 | 124 | .. code-block:: jinja 125 | 126 | 127 | {{ dropzone.load_css() }} 128 | {{ dropzone.style('border: 2px dashed #0087F7; margin: 10%; min-height: 400px;', id='foo') }} 129 | {{ dropzone.style('border: 4px dashed #0087F7; margin: 20%; min-height: 600px;', id='bar') }} 130 | 131 | 132 | Save Uploads with Flask 133 | ----------------------- 134 | 135 | When the file was dropped on drop zone, you can get the uploaded file 136 | in ``request.files``, just pass upload input's name attribute (default to ``file``). 137 | 138 | .. code-block:: python 139 | 140 | import os 141 | 142 | from flask import Flask, request 143 | from flask_dropzone import Dropzone 144 | 145 | app = Flask(__name__) 146 | 147 | dropzone = Dropzone(app) 148 | 149 | @app.route('/uploads', methods=['GET', 'POST']) 150 | def upload(): 151 | 152 | if request.method == 'POST': 153 | f = request.files.get('file') 154 | f.save(os.path.join('the/path/to/save', f.filename)) 155 | 156 | return 'upload template' 157 | 158 | 159 | .. tip:: See ``examples/basic`` for more detail. 160 | 161 | -------------------------------------------------------------------------------- /docs/_themes/flask_theme_support.py: -------------------------------------------------------------------------------- 1 | # flasky extensions. flasky pygments style based on tango style 2 | from pygments.style import Style 3 | from pygments.token import Keyword, Name, Comment, String, Error, \ 4 | Number, Operator, Generic, Whitespace, Punctuation, Other, Literal 5 | 6 | 7 | class FlaskyStyle(Style): 8 | background_color = "#f8f8f8" 9 | default_style = "" 10 | 11 | styles = { 12 | # No corresponding class for the following: 13 | #Text: "", # class: '' 14 | Whitespace: "underline #f8f8f8", # class: 'w' 15 | Error: "#a40000 border:#ef2929", # class: 'err' 16 | Other: "#000000", # class 'x' 17 | 18 | Comment: "italic #8f5902", # class: 'c' 19 | Comment.Preproc: "noitalic", # class: 'cp' 20 | 21 | Keyword: "bold #004461", # class: 'k' 22 | Keyword.Constant: "bold #004461", # class: 'kc' 23 | Keyword.Declaration: "bold #004461", # class: 'kd' 24 | Keyword.Namespace: "bold #004461", # class: 'kn' 25 | Keyword.Pseudo: "bold #004461", # class: 'kp' 26 | Keyword.Reserved: "bold #004461", # class: 'kr' 27 | Keyword.Type: "bold #004461", # class: 'kt' 28 | 29 | Operator: "#582800", # class: 'o' 30 | Operator.Word: "bold #004461", # class: 'ow' - like keywords 31 | 32 | Punctuation: "bold #000000", # class: 'p' 33 | 34 | # because special names such as Name.Class, Name.Function, etc. 35 | # are not recognized as such later in the parsing, we choose them 36 | # to look the same as ordinary variables. 37 | Name: "#000000", # class: 'n' 38 | Name.Attribute: "#c4a000", # class: 'na' - to be revised 39 | Name.Builtin: "#004461", # class: 'nb' 40 | Name.Builtin.Pseudo: "#3465a4", # class: 'bp' 41 | Name.Class: "#000000", # class: 'nc' - to be revised 42 | Name.Constant: "#000000", # class: 'no' - to be revised 43 | Name.Decorator: "#888", # class: 'nd' - to be revised 44 | Name.Entity: "#ce5c00", # class: 'ni' 45 | Name.Exception: "bold #cc0000", # class: 'ne' 46 | Name.Function: "#000000", # class: 'nf' 47 | Name.Property: "#000000", # class: 'py' 48 | Name.Label: "#f57900", # class: 'nl' 49 | Name.Namespace: "#000000", # class: 'nn' - to be revised 50 | Name.Other: "#000000", # class: 'nx' 51 | Name.Tag: "bold #004461", # class: 'nt' - like a keyword 52 | Name.Variable: "#000000", # class: 'nv' - to be revised 53 | Name.Variable.Class: "#000000", # class: 'vc' - to be revised 54 | Name.Variable.Global: "#000000", # class: 'vg' - to be revised 55 | Name.Variable.Instance: "#000000", # class: 'vi' - to be revised 56 | 57 | Number: "#990000", # class: 'm' 58 | 59 | Literal: "#000000", # class: 'l' 60 | Literal.Date: "#000000", # class: 'ld' 61 | 62 | String: "#4e9a06", # class: 's' 63 | String.Backtick: "#4e9a06", # class: 'sb' 64 | String.Char: "#4e9a06", # class: 'sc' 65 | String.Doc: "italic #8f5902", # class: 'sd' - like a comment 66 | String.Double: "#4e9a06", # class: 's2' 67 | String.Escape: "#4e9a06", # class: 'se' 68 | String.Heredoc: "#4e9a06", # class: 'sh' 69 | String.Interpol: "#4e9a06", # class: 'si' 70 | String.Other: "#4e9a06", # class: 'sx' 71 | String.Regex: "#4e9a06", # class: 'sr' 72 | String.Single: "#4e9a06", # class: 's1' 73 | String.Symbol: "#4e9a06", # class: 'ss' 74 | 75 | Generic: "#000000", # class: 'g' 76 | Generic.Deleted: "#a40000", # class: 'gd' 77 | Generic.Emph: "italic #000000", # class: 'ge' 78 | Generic.Error: "#ef2929", # class: 'gr' 79 | Generic.Heading: "bold #000080", # class: 'gh' 80 | Generic.Inserted: "#00A000", # class: 'gi' 81 | Generic.Output: "#888", # class: 'go' 82 | Generic.Prompt: "#745334", # class: 'gp' 83 | Generic.Strong: "bold #000000", # class: 'gs' 84 | Generic.Subheading: "bold #800080", # class: 'gu' 85 | Generic.Traceback: "bold #a40000", # class: 'gt' 86 | } 87 | -------------------------------------------------------------------------------- /docs/advanced.rst: -------------------------------------------------------------------------------- 1 | Advanced Usage 2 | =============== 3 | 4 | Parallel Uploads 5 | ---------------- 6 | 7 | If you set ``DROPZONE_UPLOAD_MULTIPLE`` as True, then you need to save 8 | multiple uploads in single request. 9 | 10 | However, you can't get a list of file with 11 | ``request.files.getlist('file')``. When you enable parallel upload, 12 | Dropzone.js will append a index number after each files, for example: 13 | ``file[2]``, ``file[1]``, ``file[0]``. So, you have to save files like 14 | this: 15 | 16 | .. code-block:: python 17 | 18 | for key, f in request.files.items(): 19 | if key.startswith('file'): 20 | f.save(os.path.join('the/path/to/save', f.filename)) 21 | 22 | Here is the full example: 23 | 24 | .. code-block:: python 25 | 26 | ... 27 | app.config['DROPZONE_UPLOAD_MULTIPLE'] = True # enable parallel upload 28 | app.config['DROPZONE_PARALLEL_UPLOADS'] = 3 # handle 3 file per request 29 | 30 | @app.route('/upload', methods=['GET', 'POST']) 31 | def upload(): 32 | if request.method == 'POST': 33 | for key, f in request.files.items(): 34 | if key.startswith('file'): 35 | f.save(os.path.join('the/path/to/save', f.filename)) 36 | 37 | return 'upload template' 38 | 39 | .. tip:: See ``examples/parallel-upload`` for more detail. 40 | 41 | CSRF Protect 42 | ------------ 43 | 44 | The CSRF Protect feature was provided by Flask-WTF's ``CSRFProtect`` 45 | extension, so you have to install Flask-WTF first: 46 | 47 | .. code-block:: bash 48 | 49 | $ pip install flask-wtf 50 | 51 | Then initialize the CSRFProtect: 52 | 53 | .. code-block:: python 54 | 55 | from flask_wtf.csrf import CSRFProtect 56 | 57 | app = Flask(__name__) 58 | 59 | # the secret key used to generate CSRF token 60 | app.config['SECRET_KEY'] = 'dev key' 61 | ... 62 | # enable CSRF protection 63 | app.config['DROPZONE_ENABLE_CSRF'] = True 64 | 65 | csrf = CSRFProtect(app) 66 | 67 | Make sure to set the secret key and set ``DROPZONE_ENABLE_CSRF`` to 68 | True. Now all the upload request will be protected! 69 | 70 | We prefer to handle the CSRF error manually, because the error 71 | response's body will be displayed as tooltip below the file thumbnail. 72 | 73 | .. code-block:: python 74 | 75 | from flask_wtf.csrf import CSRFProtect, CSRFError 76 | ... 77 | 78 | # handle CSRF error 79 | @app.errorhandler(CSRFError) 80 | def csrf_error(e): 81 | return e.description, 400 82 | 83 | Here I use the ``e.description`` as error message, it's provided by 84 | CSRFProtect, one of ``The CSRF token is missing`` and 85 | ``The CSRF token is invaild``. 86 | 87 | Try the demo application in ``examples/csrf`` and see `CSRFProtect's 88 | documentation `__ 89 | for more details. 90 | 91 | Content Security Policy 92 | ----------------------- 93 | 94 | If you like to use your web application under a strict `Content Security Policy `__ (CSP), just embedding JavaScript code via ``{{ dropzone.config() }}`` into a template will not work. You could move the configuration code into a separate JavaScript file and reference this resource from your HTML page. However, when you like to enable a CSRF protection as well, you need to handle the CSRF token and the CSP nonce value. The simple solution is to embed the configuration code into the HTML page and pass a ``nonce`` value for CSP as shown below: 95 | 96 | .. code-block:: python 97 | 98 | import base64 99 | import os 100 | 101 | default_http_header = {'Content-Security-Policy' : 102 | f"default-src 'self'; script-src 'self' 'nonce-{nonce}'" 103 | 104 | nonce = base64.b64encode(os.urandom(64)).decode('utf8') 105 | render_template('template.tmpl', nonce = nonce), 200, default_http_header 106 | 107 | 108 | .. code-block:: jinja 109 | 110 | {{ dropzone.config(nonce=nonce) }} 111 | 112 | Server Side Validation 113 | ---------------------- 114 | 115 | Although Dropzone.js can handle client side validation for uploads, but 116 | you still need to setup server side validation for security concern. Just 117 | do what you normally do (extension check, size check etc.), the only 118 | thing you should remember is to return plain text error message as 119 | response body when something was wrong. Fox example, if we only want 120 | user to upload file with ``.png`` extension, we can do the validation 121 | like this: 122 | 123 | .. code-block:: python 124 | 125 | @app.route('/', methods=['POST', 'GET']) 126 | def upload(): 127 | if request.method == 'POST': 128 | f = request.files.get('file') 129 | if f.filename.split('.')[1] != 'png': 130 | return 'PNG only!', 400 # return the error message, with a proper 4XX code 131 | f.save(os.path.join('the/path/to/save', f.filename)) 132 | return render_template('index.html') 133 | 134 | The error message will be displayed when you hover the thumbnail for 135 | upload file: 136 | 137 | .. figure:: ../resources/validation.png 138 | :alt: error message 139 | 140 | error message 141 | 142 | -------------------------------------------------------------------------------- /docs/_themes/flask_small/static/flasky.css_t: -------------------------------------------------------------------------------- 1 | /* 2 | * flasky.css_t 3 | * ~~~~~~~~~~~~ 4 | * 5 | * Sphinx stylesheet -- flasky theme based on nature theme. 6 | * 7 | * :copyright: Copyright 2007-2010 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | @import url("basic.css"); 13 | 14 | /* -- page layout ----------------------------------------------------------- */ 15 | 16 | body { 17 | font-family: 'Georgia', serif; 18 | font-size: 17px; 19 | color: #000; 20 | background: white; 21 | margin: 0; 22 | padding: 0; 23 | } 24 | 25 | div.documentwrapper { 26 | float: left; 27 | width: 100%; 28 | } 29 | 30 | div.bodywrapper { 31 | margin: 40px auto 0 auto; 32 | width: 700px; 33 | } 34 | 35 | hr { 36 | border: 1px solid #B1B4B6; 37 | } 38 | 39 | div.body { 40 | background-color: #ffffff; 41 | color: #3E4349; 42 | padding: 0 30px 30px 30px; 43 | } 44 | 45 | img.floatingflask { 46 | padding: 0 0 10px 10px; 47 | float: right; 48 | } 49 | 50 | div.footer { 51 | text-align: right; 52 | color: #888; 53 | padding: 10px; 54 | font-size: 14px; 55 | width: 650px; 56 | margin: 0 auto 40px auto; 57 | } 58 | 59 | div.footer a { 60 | color: #888; 61 | text-decoration: underline; 62 | } 63 | 64 | div.related { 65 | line-height: 32px; 66 | color: #888; 67 | } 68 | 69 | div.related ul { 70 | padding: 0 0 0 10px; 71 | } 72 | 73 | div.related a { 74 | color: #444; 75 | } 76 | 77 | /* -- body styles ----------------------------------------------------------- */ 78 | 79 | a { 80 | color: #004B6B; 81 | text-decoration: underline; 82 | } 83 | 84 | a:hover { 85 | color: #6D4100; 86 | text-decoration: underline; 87 | } 88 | 89 | div.body { 90 | padding-bottom: 40px; /* saved for footer */ 91 | } 92 | 93 | div.body h1, 94 | div.body h2, 95 | div.body h3, 96 | div.body h4, 97 | div.body h5, 98 | div.body h6 { 99 | font-family: 'Garamond', 'Georgia', serif; 100 | font-weight: normal; 101 | margin: 30px 0px 10px 0px; 102 | padding: 0; 103 | } 104 | 105 | {% if theme_index_logo %} 106 | div.indexwrapper h1 { 107 | text-indent: -999999px; 108 | background: url({{ theme_index_logo }}) no-repeat center center; 109 | height: {{ theme_index_logo_height }}; 110 | } 111 | {% endif %} 112 | 113 | div.body h2 { font-size: 180%; } 114 | div.body h3 { font-size: 150%; } 115 | div.body h4 { font-size: 130%; } 116 | div.body h5 { font-size: 100%; } 117 | div.body h6 { font-size: 100%; } 118 | 119 | a.headerlink { 120 | color: white; 121 | padding: 0 4px; 122 | text-decoration: none; 123 | } 124 | 125 | a.headerlink:hover { 126 | color: #444; 127 | background: #eaeaea; 128 | } 129 | 130 | div.body p, div.body dd, div.body li { 131 | line-height: 1.4em; 132 | } 133 | 134 | div.admonition { 135 | background: #fafafa; 136 | margin: 20px -30px; 137 | padding: 10px 30px; 138 | border-top: 1px solid #ccc; 139 | border-bottom: 1px solid #ccc; 140 | } 141 | 142 | div.admonition p.admonition-title { 143 | font-family: 'Garamond', 'Georgia', serif; 144 | font-weight: normal; 145 | font-size: 24px; 146 | margin: 0 0 10px 0; 147 | padding: 0; 148 | line-height: 1; 149 | } 150 | 151 | div.admonition p.last { 152 | margin-bottom: 0; 153 | } 154 | 155 | div.highlight{ 156 | background-color: white; 157 | } 158 | 159 | dt:target, .highlight { 160 | background: #FAF3E8; 161 | } 162 | 163 | div.note { 164 | background-color: #eee; 165 | border: 1px solid #ccc; 166 | } 167 | 168 | div.seealso { 169 | background-color: #ffc; 170 | border: 1px solid #ff6; 171 | } 172 | 173 | div.topic { 174 | background-color: #eee; 175 | } 176 | 177 | div.warning { 178 | background-color: #ffe4e4; 179 | border: 1px solid #f66; 180 | } 181 | 182 | p.admonition-title { 183 | display: inline; 184 | } 185 | 186 | p.admonition-title:after { 187 | content: ":"; 188 | } 189 | 190 | pre, tt { 191 | font-family: 'Consolas', 'Menlo', 'Deja Vu Sans Mono', 'Bitstream Vera Sans Mono', monospace; 192 | font-size: 0.85em; 193 | } 194 | 195 | img.screenshot { 196 | } 197 | 198 | tt.descname, tt.descclassname { 199 | font-size: 0.95em; 200 | } 201 | 202 | tt.descname { 203 | padding-right: 0.08em; 204 | } 205 | 206 | img.screenshot { 207 | -moz-box-shadow: 2px 2px 4px #eee; 208 | -webkit-box-shadow: 2px 2px 4px #eee; 209 | box-shadow: 2px 2px 4px #eee; 210 | } 211 | 212 | table.docutils { 213 | border: 1px solid #888; 214 | -moz-box-shadow: 2px 2px 4px #eee; 215 | -webkit-box-shadow: 2px 2px 4px #eee; 216 | box-shadow: 2px 2px 4px #eee; 217 | } 218 | 219 | table.docutils td, table.docutils th { 220 | border: 1px solid #888; 221 | padding: 0.25em 0.7em; 222 | } 223 | 224 | table.field-list, table.footnote { 225 | border: none; 226 | -moz-box-shadow: none; 227 | -webkit-box-shadow: none; 228 | box-shadow: none; 229 | } 230 | 231 | table.footnote { 232 | margin: 15px 0; 233 | width: 100%; 234 | border: 1px solid #eee; 235 | } 236 | 237 | table.field-list th { 238 | padding: 0 0.8em 0 0; 239 | } 240 | 241 | table.field-list td { 242 | padding: 0; 243 | } 244 | 245 | table.footnote td { 246 | padding: 0.5em; 247 | } 248 | 249 | dl { 250 | margin: 0; 251 | padding: 0; 252 | } 253 | 254 | dl dd { 255 | margin-left: 30px; 256 | } 257 | 258 | pre { 259 | padding: 0; 260 | margin: 15px -30px; 261 | padding: 8px; 262 | line-height: 1.3em; 263 | padding: 7px 30px; 264 | background: #eee; 265 | border-radius: 2px; 266 | -moz-border-radius: 2px; 267 | -webkit-border-radius: 2px; 268 | } 269 | 270 | dl pre { 271 | margin-left: -60px; 272 | padding-left: 60px; 273 | } 274 | 275 | tt { 276 | background-color: #ecf0f3; 277 | color: #222; 278 | /* padding: 1px 2px; */ 279 | } 280 | 281 | tt.xref, a tt { 282 | background-color: #FBFBFB; 283 | } 284 | 285 | a:hover tt { 286 | background: #EEE; 287 | } 288 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Configuration file for the Sphinx documentation builder. 4 | # 5 | # This file does only contain a selection of the most common options. For a 6 | # full list see the documentation: 7 | # http://www.sphinx-doc.org/en/master/config 8 | 9 | # -- Path setup -------------------------------------------------------------- 10 | 11 | # If extensions (or modules to document with autodoc) are in another directory, 12 | # add these directories to sys.path here. If the directory is relative to the 13 | # documentation root, use os.path.abspath to make it absolute, like shown here. 14 | # 15 | # import os 16 | # import sys 17 | # sys.path.insert(0, os.path.abspath('.')) 18 | 19 | 20 | # -- Project information ----------------------------------------------------- 21 | 22 | project = u'Flask-Dropzone' 23 | copyright = u'2017, Grey Li' 24 | author = u'Grey Li' 25 | 26 | # The short X.Y version 27 | version = u'1.5' 28 | # The full version, including alpha/beta/rc tags 29 | release = u'1.5.1' 30 | 31 | 32 | # -- General configuration --------------------------------------------------- 33 | 34 | # If your documentation needs a minimal Sphinx version, state it here. 35 | # 36 | # needs_sphinx = '1.0' 37 | 38 | # Add any Sphinx extension module names here, as strings. They can be 39 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 40 | # ones. 41 | extensions = [ 42 | 'sphinx.ext.autodoc', 43 | 'sphinx.ext.todo', 44 | 'sphinx.ext.coverage', 45 | ] 46 | 47 | # Add any paths that contain templates here, relative to this directory. 48 | templates_path = ['_templates'] 49 | 50 | # The suffix(es) of source filenames. 51 | # You can specify multiple suffix as a list of string: 52 | # 53 | # source_suffix = ['.rst', '.md'] 54 | source_suffix = '.rst' 55 | 56 | # The master toctree document. 57 | master_doc = 'index' 58 | 59 | # The language for content autogenerated by Sphinx. Refer to documentation 60 | # for a list of supported languages. 61 | # 62 | # This is also used if you do content translation via gettext catalogs. 63 | # Usually you set "language" from the command line for these cases. 64 | language = None 65 | 66 | # List of patterns, relative to source directory, that match files and 67 | # directories to ignore when looking for source files. 68 | # This pattern also affects html_static_path and html_extra_path . 69 | exclude_patterns = [u'_build', 'Thumbs.db', '.DS_Store'] 70 | 71 | # The name of the Pygments (syntax highlighting) style to use. 72 | pygments_style = 'sphinx' 73 | 74 | 75 | # -- Options for HTML output ------------------------------------------------- 76 | 77 | # The theme to use for HTML and HTML Help pages. See the documentation for 78 | # a list of builtin themes. 79 | # 80 | html_theme = 'flask' 81 | 82 | # Theme options are theme-specific and customize the look and feel of a theme 83 | # further. For a list of options available for each theme, see the 84 | # documentation. 85 | # 86 | html_theme_options = { 87 | # 'github_user': 'greyli', 88 | 'index_logo': 'flask-dropzone.png', 89 | # 'github_fork': 'helloflask/flask-ckeditor', 90 | # 'description': 'Create social share component in Jinja2 template based on share.js.', 91 | } 92 | 93 | # Add any paths that contain custom static files (such as style sheets) here, 94 | # relative to this directory. They are copied after the builtin static files, 95 | # so a file named "default.css" will overwrite the builtin "default.css". 96 | html_static_path = ['_static'] 97 | html_theme_path = ['_themes'] 98 | # Custom sidebar templates, must be a dictionary that maps document names 99 | # to template names. 100 | # 101 | # The default sidebars (for documents that don't match any pattern) are 102 | # defined by theme itself. Builtin themes are using these templates by 103 | # default: ``['localtoc.html', 'relations.html', 'sourcelink.html', 104 | # 'searchbox.html']``. 105 | # 106 | html_sidebars = { 107 | 'index': ['sidebarintro.html', 'sourcelink.html', 'searchbox.html'], 108 | '**': ['sidebarlogo.html', 'localtoc.html', 'relations.html', 109 | 'sourcelink.html', 'searchbox.html'] 110 | } 111 | # -- Options for HTMLHelp output --------------------------------------------- 112 | 113 | # Output file base name for HTML help builder. 114 | htmlhelp_basename = 'Flask-Dropzonedoc' 115 | 116 | 117 | # -- Options for LaTeX output ------------------------------------------------ 118 | 119 | latex_elements = { 120 | # The paper size ('letterpaper' or 'a4paper'). 121 | # 122 | # 'papersize': 'letterpaper', 123 | 124 | # The font size ('10pt', '11pt' or '12pt'). 125 | # 126 | # 'pointsize': '10pt', 127 | 128 | # Additional stuff for the LaTeX preamble. 129 | # 130 | # 'preamble': '', 131 | 132 | # Latex figure (float) alignment 133 | # 134 | # 'figure_align': 'htbp', 135 | } 136 | 137 | # Grouping the document tree into LaTeX files. List of tuples 138 | # (source start file, target name, title, 139 | # author, documentclass [howto, manual, or own class]). 140 | latex_documents = [ 141 | (master_doc, 'Flask-Dropzone.tex', u'Flask-Dropzone Documentation', 142 | u'Grey Li', 'manual'), 143 | ] 144 | 145 | 146 | # -- Options for manual page output ------------------------------------------ 147 | 148 | # One entry per manual page. List of tuples 149 | # (source start file, name, description, authors, manual section). 150 | man_pages = [ 151 | (master_doc, 'flask-dropzone', u'Flask-Dropzone Documentation', 152 | [author], 1) 153 | ] 154 | 155 | 156 | # -- Options for Texinfo output ---------------------------------------------- 157 | 158 | # Grouping the document tree into Texinfo files. List of tuples 159 | # (source start file, target name, title, author, 160 | # dir menu entry, description, category) 161 | texinfo_documents = [ 162 | (master_doc, 'Flask-Dropzone', u'Flask-Dropzone Documentation', 163 | author, 'Flask-Dropzone', 'One line description of project.', 164 | 'Miscellaneous'), 165 | ] 166 | 167 | 168 | # -- Extension configuration ------------------------------------------------- 169 | 170 | # -- Options for todo extension ---------------------------------------------- 171 | 172 | # If true, `todo` and `todoList` produce output, else they produce nothing. 173 | todo_include_todos = True -------------------------------------------------------------------------------- /docs/configuration.rst: -------------------------------------------------------------------------------- 1 | Configuration 2 | ============= 3 | 4 | Register Configuration 5 | ----------------------- 6 | 7 | Except ``DROPZONE_SERVE_LOCAL``, when you use other configuration variable, 8 | you have to call ``dropzone.config()`` in template to make them register with Dropzone: 9 | 10 | .. code-block:: jinja 11 | 12 | 13 | ... 14 | {{ dropzone.config() }} 15 | 16 | 17 | .. tip:: Call this method after ``dropzone.load_js()`` or ``\n' % url_for( 61 | "dropzone.static", filename=js_filename 62 | ) 63 | else: 64 | js = ( 65 | '\n' 66 | % (version, js_filename) 67 | ) 68 | 69 | if js_url: 70 | js = '\n' % js_url 71 | return Markup(js) 72 | 73 | @staticmethod 74 | def config( 75 | redirect_url=None, 76 | custom_init="", 77 | custom_options="", 78 | nonce=None, 79 | id="myDropzone", 80 | **kwargs, 81 | ): 82 | """Initialize dropzone configuration. 83 | 84 | .. versionchanged:: 1.5.4 85 | Added ``id`` parameter. 86 | 87 | .. versionadded:: 1.4.4 88 | 89 | :param redirect_url: The URL to redirect when upload complete. 90 | :param custom_init: Custom javascript code in ``init: function() {}``. 91 | :param custom_options: Custom javascript code in ``Dropzone.options.myDropzone = {}``. 92 | :param nonce: Pass a nonce value that is newhen embedding the JavaScript code into 93 | a Content Security Policy protected web page. 94 | :param id: The id of the dropzone element, it must matches the ``id`` argument passed to 95 | ``dropzone.create()`` if provided. 96 | :param **kwargs: Mirror configuration variable, lowercase and without prefix. 97 | For example, ``DROPZONE_UPLOAD_MULTIPLE`` becomes ``upload_multiple`` here. 98 | """ 99 | if custom_init and not custom_init.strip().endswith(";"): 100 | custom_init += ";" 101 | 102 | if custom_options and not custom_options.strip().endswith(","): 103 | custom_options += "," 104 | 105 | upload_multiple = kwargs.get( 106 | "upload_multiple", current_app.config["DROPZONE_UPLOAD_MULTIPLE"] 107 | ) 108 | parallel_uploads = kwargs.get( 109 | "parallel_uploads", current_app.config["DROPZONE_PARALLEL_UPLOADS"] 110 | ) 111 | 112 | upload_multiple = ( 113 | "true" if upload_multiple in [True, "true", "True", 1] else "false" 114 | ) 115 | 116 | size = kwargs.get("max_file_size", current_app.config["DROPZONE_MAX_FILE_SIZE"]) 117 | param = kwargs.get("input_name", current_app.config["DROPZONE_INPUT_NAME"]) 118 | redirect_view = kwargs.get( 119 | "redirect_view", current_app.config["DROPZONE_REDIRECT_VIEW"] 120 | ) 121 | 122 | if redirect_view is not None or redirect_url is not None: 123 | redirect_url = redirect_url or url_for(redirect_view) 124 | redirect_js = ( 125 | """ 126 | this.on("queuecomplete", function(file) { 127 | // Called when all files in the queue finish uploading. 128 | window.location = "%s"; 129 | });""" 130 | % redirect_url 131 | ) 132 | else: 133 | redirect_js = "" 134 | 135 | max_files = kwargs.get("max_files", current_app.config["DROPZONE_MAX_FILES"]) 136 | 137 | click_upload = kwargs.get( 138 | "upload_on_click", current_app.config["DROPZONE_UPLOAD_ON_CLICK"] 139 | ) 140 | button_id = kwargs.get( 141 | "upload_btn_id", current_app.config["DROPZONE_UPLOAD_BTN_ID"] 142 | ) 143 | in_form = kwargs.get("in_form", current_app.config["DROPZONE_IN_FORM"]) 144 | cancelUpload = kwargs.get( 145 | "cancel_upload", current_app.config["DROPZONE_CANCEL_UPLOAD"] 146 | ) 147 | removeFile = kwargs.get( 148 | "remove_file", current_app.config["DROPZONE_REMOVE_FILE"] 149 | ) 150 | cancelConfirmation = kwargs.get( 151 | "cancel_confirmation", current_app.config["DROPZONE_CANCEL_CONFIRMATION"] 152 | ) 153 | uploadCanceled = kwargs.get( 154 | "upload_canceled", current_app.config["DROPZONE_UPLOAD_CANCELED"] 155 | ) 156 | 157 | if click_upload: 158 | if in_form: 159 | action = get_url( 160 | kwargs.get( 161 | "upload_action", current_app.config["DROPZONE_UPLOAD_ACTION"] 162 | ) 163 | ) 164 | 165 | click_listener = """ 166 | dz = this; // Makes sure that 'this' is understood inside the functions below. 167 | 168 | document.getElementById("%s").addEventListener("click", function handler(e) { 169 | e.currentTarget.removeEventListener(e.type, handler); 170 | e.preventDefault(); 171 | e.stopPropagation(); 172 | dz.processQueue(); 173 | }); 174 | this.on("queuecomplete", function(file) { 175 | // Called when all files in the queue finish uploading. 176 | document.getElementById("%s").click(); 177 | }); 178 | """ % (button_id, button_id) 179 | click_option = ( 180 | """ 181 | url: "%s", 182 | autoProcessQueue: false, 183 | // addRemoveLinks: true, 184 | """ 185 | % action 186 | ) 187 | else: 188 | click_listener = ( 189 | """ 190 | dz = this; 191 | document.getElementById("%s").addEventListener("click", function handler(e) {dz.processQueue();}); 192 | """ 193 | % button_id 194 | ) 195 | 196 | click_option = """ 197 | autoProcessQueue: false, 198 | // addRemoveLinks: true, 199 | """ 200 | upload_multiple = "true" 201 | parallel_uploads = ( 202 | max_files if isinstance(max_files, int) else parallel_uploads 203 | ) 204 | else: 205 | click_listener = "" 206 | click_option = "" 207 | 208 | allowed_file_type = kwargs.get( 209 | "allowed_file_type", current_app.config["DROPZONE_ALLOWED_FILE_TYPE"] 210 | ) 211 | allowed_file_custom = kwargs.get( 212 | "allowed_file_custom", current_app.config["DROPZONE_ALLOWED_FILE_CUSTOM"] 213 | ) 214 | 215 | allowed_type = ( 216 | allowed_file_type 217 | if allowed_file_custom 218 | else allowed_file_extensions[allowed_file_type] 219 | ) 220 | 221 | default_message = kwargs.get( 222 | "default_message", current_app.config["DROPZONE_DEFAULT_MESSAGE"] 223 | ) 224 | invalid_file_type = kwargs.get( 225 | "invalid_file_type", current_app.config["DROPZONE_INVALID_FILE_TYPE"] 226 | ) 227 | file_too_big = kwargs.get( 228 | "file_too_big", current_app.config["DROPZONE_FILE_TOO_BIG"] 229 | ) 230 | server_error = kwargs.get( 231 | "server_error", current_app.config["DROPZONE_SERVER_ERROR"] 232 | ) 233 | browser_unsupported = kwargs.get( 234 | "browser_unsupported", current_app.config["DROPZONE_BROWSER_UNSUPPORTED"] 235 | ) 236 | max_files_exceeded = kwargs.get( 237 | "max_file_exceeded", current_app.config["DROPZONE_MAX_FILE_EXCEED"] 238 | ) 239 | 240 | timeout = kwargs.get("timeout", current_app.config["DROPZONE_TIMEOUT"]) 241 | if timeout: 242 | custom_options += "timeout: %d," % timeout 243 | 244 | enable_csrf = kwargs.get( 245 | "enable_csrf", current_app.config["DROPZONE_ENABLE_CSRF"] 246 | ) 247 | if enable_csrf: 248 | if "csrf" not in current_app.extensions: 249 | raise RuntimeError( 250 | "CSRFProtect is not initialized. It's required to enable CSRF protect, \ 251 | see docs for more details." 252 | ) 253 | csrf_token = render_template_string("{{ csrf_token() }}") 254 | custom_options += 'headers: {"X-CSRF-Token": "%s"},' % csrf_token 255 | 256 | nonce_html = ' nonce="%s"' % nonce if nonce else "" 257 | 258 | return Markup( 259 | """ 260 | Dropzone.options.%s = { 261 | init: function() { 262 | %s // redirect after queue complete 263 | %s // upload queue when button click 264 | %s // custom init code 265 | }, 266 | %s // click upload options 267 | uploadMultiple: %s, 268 | parallelUploads: %d, 269 | paramName: "%s", // The name that will be used to transfer the file 270 | maxFilesize: %d, // MB 271 | acceptedFiles: "%s", 272 | maxFiles: %s, 273 | dictDefaultMessage: `%s`, // message display on drop area 274 | dictFallbackMessage: "%s", 275 | dictInvalidFileType: "%s", 276 | dictFileTooBig: "%s", 277 | dictResponseError: "%s", 278 | dictMaxFilesExceeded: "%s", 279 | dictCancelUpload: "%s", 280 | dictRemoveFile: "%s", 281 | dictCancelUploadConfirmation: "%s", 282 | dictUploadCanceled: "%s", 283 | %s // custom options code 284 | }; 285 | 286 | """ 287 | % ( 288 | nonce_html, 289 | id, 290 | redirect_js, 291 | click_listener, 292 | custom_init, 293 | click_option, 294 | upload_multiple, 295 | parallel_uploads, 296 | param, 297 | size, 298 | allowed_type, 299 | max_files, 300 | default_message, 301 | browser_unsupported, 302 | invalid_file_type, 303 | file_too_big, 304 | server_error, 305 | max_files_exceeded, 306 | cancelUpload, 307 | removeFile, 308 | cancelConfirmation, 309 | uploadCanceled, 310 | custom_options, 311 | ) 312 | ) 313 | 314 | @staticmethod 315 | def create(action="", csrf=False, action_view="", id="myDropzone", **kwargs): 316 | """Create a Dropzone form with given action. 317 | 318 | .. versionchanged:: 1.4.2 319 | Added ``csrf`` parameter to enable CSRF protect. 320 | 321 | .. versionchanged:: 1.4.3 322 | Added ``action`` parameter to replace ``action_view``, ``action_view`` was deprecated now. 323 | 324 | .. versionchanged:: 1.5.0 325 | If ``DROPZONE_IN_FORM`` set to ``True``, create ``
`` instead of ````. 326 | 327 | .. versionchanged:: 1.5.4 328 | ``csrf`` was deprecated now. 329 | 330 | .. versionchanged:: 1.5.4 331 | Added ``id`` parameter. 332 | 333 | :param action: The action attribute in ````, pass the url which handle uploads. 334 | :param csrf: Enable CSRF protect or not, same with ``DROPZONE_ENABLE_CSRF``, deprecated since 1.5.4. 335 | :param action_view: The view which handle the post data, deprecated since 1.4.2. 336 | :param id: The id of the dropzone element, it must matches the ``id`` argument passed to 337 | ``dropzone.config()`` if provided. 338 | """ 339 | if current_app.config["DROPZONE_IN_FORM"]: 340 | return Markup('
') 341 | 342 | if action: 343 | action_url = get_url(action, **kwargs) 344 | else: 345 | warnings.warn( 346 | 'The argument was renamed to "action" and will be removed in 2.0.' 347 | ) 348 | action_url = url_for(action_view, **kwargs) 349 | 350 | if csrf: 351 | warnings.warn( 352 | "The argument was deprecated and will be removed in 2.0, use DROPZONE_ENABLE_CSRF instead." 353 | ) 354 | 355 | return Markup( 356 | """""" 358 | % (action_url, id) 359 | ) 360 | 361 | @staticmethod 362 | def style(css, id=None): 363 | """Add css to dropzone. 364 | 365 | .. versionchanged:: 1.7.0 366 | Added ``id`` parameter. 367 | 368 | :param css: style sheet code. 369 | :param id: The id of the dropzone element, it must matches the ``id`` argument passed to 370 | ``dropzone.create()``. 371 | """ 372 | if id is not None: 373 | return Markup("" % (id, css)) 374 | return Markup("" % css) 375 | 376 | 377 | class Dropzone(object): 378 | def __init__(self, app=None): 379 | if app is not None: 380 | self.init_app(app) 381 | 382 | def init_app(self, app): 383 | blueprint = Blueprint( 384 | "dropzone", 385 | __name__, 386 | static_folder="static", 387 | static_url_path="/dropzone" + app.static_url_path, 388 | ) 389 | app.register_blueprint(blueprint) 390 | 391 | if not hasattr(app, "extensions"): 392 | app.extensions = {} 393 | app.extensions["dropzone"] = _Dropzone 394 | app.context_processor(self.context_processor) 395 | 396 | # settings 397 | app.config.setdefault("DROPZONE_SERVE_LOCAL", False) 398 | app.config.setdefault("DROPZONE_MAX_FILE_SIZE", 3) # MB 399 | app.config.setdefault("DROPZONE_INPUT_NAME", "file") 400 | app.config.setdefault("DROPZONE_ALLOWED_FILE_CUSTOM", False) 401 | app.config.setdefault("DROPZONE_ALLOWED_FILE_TYPE", "default") 402 | app.config.setdefault("DROPZONE_MAX_FILES", "null") 403 | # The timeout to cancel upload request in millisecond, default to 30000 (30 second). 404 | # Set a large number if you need to upload large file. 405 | # .. versionadded: 1.5.0 406 | app.config.setdefault( 407 | "DROPZONE_TIMEOUT", None 408 | ) # millisecond, default to 30000 (30 second) 409 | 410 | # The view to redirect when upload was completed. 411 | # .. versionadded:: 1.4.1 412 | app.config.setdefault("DROPZONE_REDIRECT_VIEW", None) 413 | 414 | # Whether to send multiple files in one request. 415 | # In default, each file will send with a request. 416 | # Then you can use ``request.files.getlist('paramName')`` to 417 | # get a list of uploads. 418 | # .. versionadded:: 1.4.1 419 | app.config.setdefault("DROPZONE_UPLOAD_MULTIPLE", False) 420 | 421 | # When ``DROPZONE_UPLOAD_MULTIPLE`` set to True, this will 422 | # defined how many uploads will handled in per request. 423 | # .. versionadded:: 1.4.1 424 | app.config.setdefault("DROPZONE_PARALLEL_UPLOADS", 2) 425 | 426 | # When set to ``True``, it will add a csrf_token hidden field in upload form. 427 | # You have to install Flask-WTF to make it work properly, see details in docs. 428 | # .. versionadded:: 1.4.2 429 | app.config.setdefault("DROPZONE_ENABLE_CSRF", False) 430 | 431 | # Add support to upload files when button was clicked. 432 | # .. versionadded:: 1.5.0 433 | app.config.setdefault("DROPZONE_UPLOAD_ACTION", "") 434 | app.config.setdefault("DROPZONE_UPLOAD_ON_CLICK", False) 435 | app.config.setdefault("DROPZONE_UPLOAD_BTN_ID", "upload") 436 | 437 | # Add support to create dropzone inside ``
``. 438 | # .. versionadded:: 1.5.0 439 | app.config.setdefault("DROPZONE_IN_FORM", False) 440 | 441 | # messages 442 | app.config.setdefault( 443 | "DROPZONE_DEFAULT_MESSAGE", "Drop files here or click to upload." 444 | ) 445 | app.config.setdefault( 446 | "DROPZONE_INVALID_FILE_TYPE", "You can't upload files of this type." 447 | ) 448 | app.config.setdefault( 449 | "DROPZONE_FILE_TOO_BIG", 450 | "File is too big {{filesize}}. Max filesize: {{maxFilesize}}MiB.", 451 | ) 452 | app.config.setdefault("DROPZONE_SERVER_ERROR", "Server error: {{statusCode}}") 453 | app.config.setdefault( 454 | "DROPZONE_BROWSER_UNSUPPORTED", 455 | "Your browser does not support drag'n'drop file uploads.", 456 | ) 457 | app.config.setdefault( 458 | "DROPZONE_MAX_FILE_EXCEED", "You can't upload any more files." 459 | ) 460 | app.config.setdefault("DROPZONE_CANCEL_UPLOAD", "Cancel upload") 461 | app.config.setdefault("DROPZONE_REMOVE_FILE", "Remove file") 462 | app.config.setdefault( 463 | "DROPZONE_CANCEL_CONFIRMATION", "You really want to delete this file?" 464 | ) 465 | app.config.setdefault("DROPZONE_UPLOAD_CANCELED", "Upload canceled") 466 | 467 | @staticmethod 468 | def context_processor(): 469 | return {"dropzone": current_app.extensions["dropzone"]} 470 | --------------------------------------------------------------------------------