how to use flask route for uploads

A common feature in web applications is to let users upload files to the server. The HTTP protocol documents the mechanism for a client to upload a file in RFC 1867, and our favorite web framework Flask fully supports information technology, but in that location are many implementation details that fall outside of the formal specification that are unclear for many developers. Things such as where to store uploaded files, how to utilize them afterwards, or how to protect the server against malicious file uploads generate a lot of confusion and uncertainty.

In this article I'm going to show you how to implement a robust file upload feature for your Flask server that is compatible with the standard file upload support in your web browser as well as the cool JavaScript-based upload widgets:

Basic file upload form

A Bones File Upload Course

From a high-level perspective, a customer uploading a file is treated the aforementioned as whatsoever other form data submission. In other words, you take to define an HTML form with a file field in it.

Hither is a elementary HTML page with a course that accepts a file:

          <!doctype html> <html>   <caput>     <title>File Upload</title>   </head>   <body>     <h1>File Upload</h1>     <form method="Mail service" action="" enctype="multipart/class-data">       <p><input blazon="file" name="file"></p>       <p><input type="submit" value="Submit"></p>     </form>   </body> </html>                  
Basic file upload form

As you lot probably know, the method aspect of the <form> element can be Go or Mail. With GET, the data is submitted in the query cord of the request URL, while with POST information technology goes in the asking body. When files are being included in the form, you must use Postal service, as it would be impossible to submit file data in the query string.

The enctype attribute in the <class> element is commonly not included with forms that don't have files. This attribute defines how the browser should format the data earlier it is submitted to the server. The HTML specification defines iii possible values for it:

  • awarding/ten-www-form-urlencoded: This is the default, and the all-time format for any forms except those that contain file fields.
  • multipart/form-information: This format is required when at to the lowest degree one of the fields in the class is a file field.
  • text/obviously: This format has no practical use, then you should ignore it.

The actual file field is the standard <input> element that we use for most other form fields, with the type gear up to file. In the instance above I haven't included any additional attributes, but the file field supports two that are sometimes useful:

  • multiple can be used to allow multiple files to be uploaded in a single file field. Instance:
                      <input type="file" proper noun="file" multiple>                  
  • accept can exist used to filter the allowed file types that tin be selected, either by file extension or by media type. Examples:
                      <input type="file" proper noun="doc_file" accept=".medico,.docx">     <input type="file" name="image_file" accept="prototype/*">                  

Accepting File Submissions with Flask

For regular forms, Flask provides access to submitted grade fields in the asking.form dictionary. File fields, however, are included in the request.files dictionary. The request.form and asking.files dictionaries are really "multi-dicts", a specialized dictionary implementation that supports indistinguishable keys. This is necessary because forms can include multiple fields with the same name, as is often the case with groups of check boxes. This also happens with file fields that allow multiple files.

Ignoring important aspects such as validation and security for the moment, the short Flask awarding shown below accepts a file uploaded with the form shown in the previous section, and writes the submitted file to the electric current directory:

          from flask import Flask, render_template, request, redirect, url_for  app = Flask(__name__)  @app.route('/') def index():     return render_template('index.html')  @app.route('/', methods=['POST']) def upload_file():     uploaded_file = request.files['file']     if uploaded_file.filename != '':         uploaded_file.salve(uploaded_file.filename)     return redirect(url_for('index'))                  

The upload_file() function is decorated with @app.route so that it is invoked when the browser sends a POST request. Notation how the same root URL is separate between ii view functions, with index() set to take the Go requests and upload_file() the Mail service ones.

The uploaded_file variable holds the submitted file object. This is an case of class FileStorage, which Flask imports from Werkzeug.

The filename attribute in the FileStorage provides the filename submitted past the customer. If the user submits the grade without selecting a file in the file field, then the filename is going to be an empty cord, so it is important to always check the filename to make up one's mind if a file is available or non.

When Flask receives a file submission it does non automatically write it to disk. This is actually a adept thing, because it gives the application the opportunity to review and validate the file submission, every bit you will see later. The actual file data can exist accessed from the stream attribute. If the application just wants to save the file to disk, then it can call the save() method, passing the desired path as an argument. If the file's salvage() method is non chosen, so the file is discarded.

Want to test file uploads with this application? Make a directory for your application and write the code above as app.py. Then create a templates subdirectory, and write the HTML page from the previous section as templates/alphabetize.html. Create a virtual surround and install Flask on information technology, then run the application with flask run. Every fourth dimension you submit a file, the server will write a copy of it in the current directory.

Before I move on to the topic of security, I'm going to discuss a few variations on the code shown in a higher place that y'all may observe useful. As I mentioned earlier, the file upload field tin be configured to accept multiple files. If you employ asking.files['file'] as above you volition get but one of the submitted files, merely with the getlist() method yous can access all of them in a for-loop:

                      for uploaded_file in request.files.getlist('file'):         if uploaded_file.filename != '':             uploaded_file.salvage(uploaded_file.filename)                  

Many people lawmaking their form treatment routes in Flask using a single view office for both the GET and POST requests. A version of the example application using a single view function could be coded equally follows:

          @app.route('/', methods=['Go', 'POST']) def index():     if request.method == 'Postal service':         uploaded_file = request.files['file']         if uploaded_file.filename != '':             uploaded_file.salve(uploaded_file.filename)         return redirect(url_for('index'))     render render_template('index.html')                  

Finally, if you use the Flask-WTF extension to handle your forms, yous can utilise the FileField object for your file uploads. The grade used in the examples you've seen so far can be written using Flask-WTF every bit follows:

          from flask_wtf import FlaskForm from flask_wtf.file import FileField from wtforms import SubmitField  class MyForm(FlaskForm):     file = FileField('File')     submit = SubmitField('Submit')                  

Note that the FileField object comes from the flask_wtf package, unlike most other field classes, which are imported direct from the wtforms package. Flask-WTF provides two validators for file fields, FileRequired, which performs a check similar to the empty string cheque, and FileAllowed, which ensures the file extension is included in an immune extensions list.

When you employ a Flask-WTF grade, the data attribute of the file field object points to the FileStorage instance, and then saving a file to disk works in the same mode as in the examples in a higher place.

Securing file uploads

The file upload example presented in the previous section is an extremely simplistic implementation that is non very robust. I of the well-nigh important rules in web evolution is that data submitted by clients should never be trusted, and for that reason when working with regular forms, an extension such every bit Flask-WTF performs strict validation of all fields earlier the class is accepted and the data incorporated into the application. For forms that include file fields there needs to exist validation too, because without file validation the server leaves the door open to attacks. For example:

  • An assaulter can upload a file that is so large that the disk space in the server is completely filled, causing the server to malfunction.
  • An attacker can craft an upload request that uses a filename such as ../../../.bashrc or similar, with the attempt to play tricks the server into rewriting arrangement configuration files.
  • An assailant tin can upload files with viruses or other types of malware in a place where the awarding, for example, expects images.

Limiting the size of uploaded files

To prevent clients from uploading very big files, you can use a configuration option provided past Flask. The MAX_CONTENT_LENGTH option controls the maximum size a request body tin can have. While this isn't an option that is specific to file uploads, setting a maximum asking body size effectively makes Flask discard any incoming requests that are larger than the allowed amount with a 413 condition lawmaking.

Let's modify the app.py instance from the previous section to merely accept requests that are upwards to 1MB in size:

          app.config['MAX_CONTENT_LENGTH'] = 1024 * 1024                  

If you lot try to upload a file that is larger than 1MB, the application will now reject it.

Validating filenames

We can't really trust that the filenames provided by the client are valid and safe to use, and then filenames coming with uploaded files have to be validated.

A very simple validation to perform is to make sure that the file extension is i that the application is willing to accept, which is similar to what the FileAllowed validator does when using Flask-WTF. Let's say the application accepts images, then information technology tin configure the listing of approved file extensions:

          app.config['UPLOAD_EXTENSIONS'] = ['.jpg', '.png', '.gif']                  

For every uploaded file, the application can make sure that the file extension is one of the allowed ones:

                      filename = uploaded_file.filename     if filename != '':         file_ext = os.path.splitext(filename)[1]         if file_ext not in current_app.config['UPLOAD_EXTENSIONS']:             abort(400)                  

With this logic, whatsoever filenames that practise not have one of the approved file extensions is going to be responded with a 400 mistake.

In addition to the file extension, it is as well important to validate the filename, and whatsoever path given with information technology. If your application does not intendance near the filename provided by the customer, the nigh secure way to handle the upload is to ignore the client provided filename and generate your ain filename instead, that you pass to the salvage() method. An case employ example where this technique works well is with avatar image uploads. Each user's avatar can be saved with the user id as filename, so the filename provided by the customer can be discarded. If your application uses Flask-Login, you could implement the following salve() call:

          uploaded_file.save(os.path.join('static/avatars', current_user.get_id()))                  

In other cases it may exist amend to preserve the filenames provided by the client, so the filename must be sanitized offset. For those cases Werkzeug provides the secure_filename() role. Permit's encounter how this function works by running a few tests in a Python session:

          >>> from werkzeug.utils import secure_filename >>> secure_filename('foo.jpg') 'foo.jpg' >>> secure_filename('/some/path/foo.jpg') 'some_path_foo.jpg' >>> secure_filename('../../../.bashrc') 'bashrc'                  

As y'all see in the examples, no matter how complicated or malicious the filename is, the secure_filename() function reduces it to a apartment filename.

Permit'south comprise secure_filename() into the example upload server, and likewise add a configuration variable that defines a dedicated location for file uploads. Hither is the complete app.py source file with secure filenames:

          import os from flask import Flask, render_template, asking, redirect, url_for, abort from werkzeug.utils import secure_filename  app = Flask(__name__) app.config['MAX_CONTENT_LENGTH'] = 1024 * 1024 app.config['UPLOAD_EXTENSIONS'] = ['.jpg', '.png', '.gif'] app.config['UPLOAD_PATH'] = 'uploads'  @app.route('/') def alphabetize():     return render_template('index.html')  @app.road('/', methods=['Postal service']) def upload_files():     uploaded_file = request.files['file']     filename = secure_filename(uploaded_file.filename)     if filename != '':         file_ext = os.path.splitext(filename)[1]         if file_ext not in app.config['UPLOAD_EXTENSIONS']:             abort(400)         uploaded_file.salvage(bone.path.join(app.config['UPLOAD_PATH'], filename))     return redirect(url_for('index'))                  

Validating file contents

The third layer of validation that I'grand going to discuss is the most circuitous. If your application accepts uploads of a sure file blazon, it should ideally perform some form of content validation and reject whatever files that are of a unlike type.

How you achieve content validation largely depends on the file types your application accepts. For the example application in this commodity I'm using images, and so I can utilise the imghdr packet from the Python standard library to validate that the header of the file is, in fact, an paradigm.

Let'south write a validate_image() office that performs content validation on images:

          import imghdr  def validate_image(stream):     header = stream.read(512)     stream.seek(0)     format = imghdr.what(None, header)     if not format:         return None     return '.' + (format if format != 'jpeg' else 'jpg')                  

This office takes a byte stream equally an argument. It starts by reading 512 bytes from the stream, and and then resetting the stream pointer back, because later when the save() function is called we want it to see the unabridged stream. The commencement 512 bytes of the image information are going to be sufficient to identify the format of the image.

The imghdr.what() office can expect at a file stored on deejay if the first argument is the filename, or else information technology can look at data stored in memory if the first argument is None and the data is passed in the second argument. The FileStorage object gives u.s.a. a stream, and so the most convenient choice is to read a safe corporeality of data from it and pass it equally a byte sequence in the 2nd statement.

The return value of imghdr.what() is the detected image format. The function supports a variety of formats, among them the popular jpeg, png and gif. If not known image format is detected, then the return value is None. If a format is detected, the proper noun of the format is returned. The most convenient is to render the format as a file extension, because the application can and so ensure that the detected extension matches the file extension, so the validate_image() office converts the detected format into a file extension. This is equally uncomplicated as adding a dot every bit prefix for all image formats except jpeg, which ordinarily uses the .jpg extension, and so this case is treated as an exception.

Hither is the complete app.py, with all the features from the previous sections plus content validation:

          import imghdr import bone from flask import Flask, render_template, asking, redirect, url_for, abort from werkzeug.utils import secure_filename  app = Flask(__name__) app.config['MAX_CONTENT_LENGTH'] = 1024 * 1024 app.config['UPLOAD_EXTENSIONS'] = ['.jpg', '.png', '.gif'] app.config['UPLOAD_PATH'] = 'uploads'  def validate_image(stream):     header = stream.read(512)     stream.seek(0)      format = imghdr.what(None, header)     if not format:         return None     return '.' + (format if format != 'jpeg' else 'jpg')  @app.route('/') def index():     return render_template('index.html')  @app.route('/', methods=['Post']) def upload_files():     uploaded_file = request.files['file']     filename = secure_filename(uploaded_file.filename)     if filename != '':         file_ext = bone.path.splitext(filename)[1]         if file_ext not in app.config['UPLOAD_EXTENSIONS'] or \                 file_ext != validate_image(uploaded_file.stream):             abort(400)         uploaded_file.save(os.path.join(app.config['UPLOAD_PATH'], filename))     return redirect(url_for('alphabetize'))                  

The only change in the view function to incorporate this last validation logic is hither:

                      if file_ext not in app.config['UPLOAD_EXTENSIONS'] or \                 file_ext != validate_image(uploaded_file.stream):             abort(400)                  

This expanded check kickoff makes sure that the file extension is in the allowed list, and and then ensures that the detected file extension from looking at the information stream is the same as the file extension.

Earlier you test this version of the awarding create a directory named uploads (or the path that you divers in the UPLOAD_PATH configuration variable, if different) then that files can exist saved there.

Using Uploaded Files

You now know how to handle file uploads. For some applications this is all that is needed, as the files are used for some internal procedure. But for a large number of applications, in particular those with social features such as avatars, the files that are uploaded by users have to be integrated with the application. Using the instance of avatars, once a user uploads their avatar image, any mention of the username requires the uploaded image to announced to the side.

I separate file uploads into ii large groups, depending on whether the files uploaded by users are intended for public utilize, or they are private to each user. The avatar images discussed several times in this article are conspicuously in the outset group, as these avatars are intended to exist publicly shared with other users. On the other side, an application that performs editing operations on uploaded images would probably exist in the second grouping, because you'd desire each user to only have access to their ain images.

Consuming public uploads

When images are of a public nature, the easiest manner to make the images bachelor for use by the awarding is to put the upload directory inside the application'southward static folder. For example, an avatars subdirectory can be created inside static, and and then avatar images can exist saved there using the user id as name.

Referencing these uploads stored in a subdirectory of the static binder is done in the same way as regular static files of the application, using the url_for() function. I previously suggested using the user id every bit a filename, when saving an uploaded avatar image. This was the way the images were saved:

          uploaded_file.salve(os.path.join('static/avatars', current_user.get_id()))                  

With this implementation, given a user_id, the URL for the user'south avatar can be generated as follows:

          url_for('static', filename='avatars/' + str(user_id))                  

Alternatively, the uploads can be saved to a directory outside of the static folder, and and so a new route tin can exist added to serve them. In the example app.py application file uploads are saved to the location ready in the UPLOAD_PATH configuration variable. To serve these files from that location, we can implement the following route:

          from flask import send_from_directory  @app.route('/uploads/<filename>') def upload(filename):     return send_from_directory(app.config['UPLOAD_PATH'], filename)                  

One advantage that this solution has over storing uploads inside the static folder is that here you lot can implement additional restrictions before these files are returned, either direct with Python logic within the body of the part, or with decorators. For example, if you want to only provide access to the uploads to logged in users, you can add Flask-Login's @login_required decorator to this road, or any other authentication or role checking mechanism that you use for your normal routes.

Let's use this implementation idea to show uploaded files in our example application. Hither is a new complete version of app.py:

          import imghdr import os from flask import Flask, render_template, asking, redirect, url_for, abort, \     send_from_directory from werkzeug.utils import secure_filename  app = Flask(__name__) app.config['MAX_CONTENT_LENGTH'] = 1024 * 1024 app.config['UPLOAD_EXTENSIONS'] = ['.jpg', '.png', '.gif'] app.config['UPLOAD_PATH'] = 'uploads'  def validate_image(stream):     header = stream.read(512)  # 512 bytes should exist plenty for a header cheque     stream.seek(0)  # reset stream pointer     format = imghdr.what(None, header)     if not format:         return None     render '.' + (format if format != 'jpeg' else 'jpg')  @app.route('/') def index():     files = os.listdir(app.config['UPLOAD_PATH'])     return render_template('alphabetize.html', files=files)  @app.road('/', methods=['Post']) def upload_files():     uploaded_file = request.files['file']     filename = secure_filename(uploaded_file.filename)     if filename != '':         file_ext = os.path.splitext(filename)[1]         if file_ext not in app.config['UPLOAD_EXTENSIONS'] or \                 file_ext != validate_image(uploaded_file.stream):             abort(400)         uploaded_file.save(os.path.join(app.config['UPLOAD_PATH'], filename))     return redirect(url_for('alphabetize'))  @app.road('/uploads/<filename>') def upload(filename):     return send_from_directory(app.config['UPLOAD_PATH'], filename)                  

In add-on to the new upload() function, the index() view office gets the list of files in the upload location using os.listdir() and sends it down to the template for rendering. The index.html template updated to prove uploads is shown below:

          <!doctype html> <html>   <head>     <championship>File Upload</title>   </head>   <body>     <h1>File Upload</h1>     <form method="POST" action="" enctype="multipart/grade-data">       <p><input type="file" name="file"></p>       <p><input blazon="submit" value="Submit"></p>     </course>     <hr>     {% for file in files %}       <img src="{{ url_for('upload', filename=file) }}" style="width: 64px">     {% endfor %}   </trunk> </html>                  

With these changes, every time you upload an prototype, a thumbnail is added at the lesser of the page:

Basic file upload form

Consuming private uploads

When users upload private files to the awarding, additional checks demand to exist in place to prevent sharing files from one user with unauthorized parties. The solution for these cases require variations of the upload() view role shown above, with additional access checks.

A common requirement is to only share uploaded files with their owner. A convenient way to shop uploads when this requirement is present is to use a separate directory for each user. For example, uploads for a given user tin can be saved to the uploads/<user_id> directory, and then the uploads() office can exist modified to only serve uploads from the user's ain upload directory, making it impossible for ane user to see files from another. Below you tin can see a possible implementation of this technique, in one case once again assuming Flask-Login is used:

          @app.route('/uploads/<filename>') @login_required def upload(filename):     render send_from_directory(bone.path.join(         app.config['UPLOAD_PATH'], current_user.get_id()), filename)                  

Showing upload progress

Upward until now nosotros have relied on the native file upload widget provided by the web browser to initiate our file uploads. I'm sure nosotros can all agree that this widget is not very highly-seasoned. Not only that, merely the lack of an upload progress display makes it unusable for uploads of big files, equally the user receives no feedback during the entire upload process. While the scope of this article is to cover the server side, I thought it would be useful to requite you a few ideas on how to implement a modern JavaScript-based file upload widget that displays upload progress.

The good news is that on the server at that place aren't any big changes needed, the upload mechanism works in the aforementioned way regardless of what method you use in the browser to initiate the upload. To prove you an case implementation I'm going to replace the HTML form in alphabetize.html with one that is uniform with dropzone.js, a popular file upload client.

Here is a new version of templates/alphabetize.html that loads the dropzone CSS and JavaScript files from a CDN, and implements an upload form according to the dropzone documentation:

          <html>   <head>     <title>File Upload</title>     <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/dropzone/five.7.1/min/dropzone.min.css">   </caput>   <body>     <h1>File Upload</h1>     <form activity="{{ url_for('upload_files') }}" form="dropzone">     </form>     <script src="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.7.i/min/dropzone.min.js"></script>   </body> </html>                  

The one interesting affair that I've institute when implementing dropzone is that it requires the activity attribute in the <course> element to be gear up, even though normal forms take an empty activity to point that the submission goes to the same URL.

Start the server with this new version of the template, and this is what you'll get:

Basic file upload form

That'southward basically it! You can now driblet files and they'll be uploaded to the server with a progress bar and a terminal indication of success or failure.

If the file upload fails, either due to the file being as well large or invalid, dropzone wants to display an error bulletin. Because our server is currently returning the standard Flask error pages for the 413 and 400 errors, you will see some HTML gibberish in the error popup. To correct this we can update the server to return its error responses as text.

The 413 fault for the file also big condition is generated by Flask when the request payload is bigger than the size fix in the configuration. To override the default error page we accept to employ the app.errorhandler decorator:

          @app.errorhandler(413) def too_large(east):     render "File is too large", 413                  

The second error status is generated by the application when any of the validation checks fails. In this case the error was generated with a abort(400) call. Instead of that the response tin be generated straight:

                      if file_ext not in app.config['UPLOAD_EXTENSIONS'] or \                 file_ext != validate_image(uploaded_file.stream):             return "Invalid image", 400                  

The final alter that I'm going to brand isn't actually necessary, but it saves a bit of bandwidth. For a successful upload the server returned a redirect() back to the principal route. This caused the upload grade to be displayed over again, and as well to refresh the list of upload thumbnails at the bottom of the page. None of that is necessary now considering the uploads are done as background requests past dropzone, and so we tin eliminate that redirect and switch to an empty response with a code 204.

Here is the complete and updated version of app.py designed to work with dropzone.js:

          import imghdr import os from flask import Flask, render_template, request, redirect, url_for, arrest, \     send_from_directory from werkzeug.utils import secure_filename  app = Flask(__name__) app.config['MAX_CONTENT_LENGTH'] = ii * 1024 * 1024 app.config['UPLOAD_EXTENSIONS'] = ['.jpg', '.png', '.gif'] app.config['UPLOAD_PATH'] = 'uploads'  def validate_image(stream):     header = stream.read(512)     stream.seek(0)     format = imghdr.what(None, header)     if not format:         return None     return '.' + (format if format != 'jpeg' else 'jpg')  @app.errorhandler(413) def too_large(due east):     return "File is also large", 413  @app.route('/') def index():     files = os.listdir(app.config['UPLOAD_PATH'])     return render_template('alphabetize.html', files=files)  @app.route('/', methods=['POST']) def upload_files():     uploaded_file = asking.files['file']     filename = secure_filename(uploaded_file.filename)     if filename != '':         file_ext = bone.path.splitext(filename)[i]         if file_ext not in app.config['UPLOAD_EXTENSIONS'] or \                 file_ext != validate_image(uploaded_file.stream):             return "Invalid image", 400         uploaded_file.save(os.path.join(app.config['UPLOAD_PATH'], filename))     return '', 204  @app.route('/uploads/<filename>') def upload(filename):     render send_from_directory(app.config['UPLOAD_PATH'], filename)                  

Restart the application with this update and now errors will have a proper message:

Basic file upload form

The dropzone.js library is very flexible and has many options for customization, so I encourage you to visit their documentation to larn how to adapt it to your needs. You tin can also wait for other JavaScript file upload libraries, as they all follow the HTTP standard, which means that your Flask server is going to work well with all of them.

Decision

This was a long overdue topic for me, I tin't believe I accept never written anything on file uploads! I'd love you hear what you remember about this topic, and if you lot call up there are aspects of this feature that I haven't covered in this article. Experience free to let me know below in the comments!

ruddfany1958.blogspot.com

Source: https://blog.miguelgrinberg.com/post/handling-file-uploads-with-flask

0 Response to "how to use flask route for uploads"

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel