Flask Upload Image and Display Same Page

A common feature in spider web applications is to let users upload files to the server. The HTTP protocol documents the mechanism for a customer to upload a file in RFC 1867, and our favorite web framework Flask fully supports it, but there are many implementation details that autumn exterior of the formal specification that are unclear for many developers. Things such as where to store uploaded files, how to use them afterwards, or how to protect the server against malicious file uploads generate a lot of defoliation and uncertainty.

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

Basic file upload form

A Basic File Upload Form

From a high-level perspective, a customer uploading a file is treated the same equally any other form data submission. In other words, you lot have to define an HTML form with a file field in it.

Here is a uncomplicated HTML page with a course that accepts a file:

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

As y'all probably know, the method attribute of the <course> element can be Go or Mail service. With Get, the information is submitted in the query string of the request URL, while with Mail service it goes in the request trunk. When files are being included in the form, you must use Postal service, as information technology would exist incommunicable to submit file data in the query cord.

The enctype attribute in the <form> element is ordinarily not included with forms that don't have files. This aspect defines how the browser should format the information earlier information technology is submitted to the server. The HTML specification defines three possible values for it:

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

The actual file field is the standard <input> chemical element that we use for almost other form fields, with the type prepare to file. In the example above I oasis't included any additional attributes, but the file field supports two that are sometimes useful:

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

Accepting File Submissions with Flask

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

Ignoring important aspects such every bit validation and security for the moment, the short Flask application shown below accepts a file uploaded with the grade shown in the previous section, and writes the submitted file to the 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.road('/', methods=['Post']) def upload_file():     uploaded_file = request.files['file']     if uploaded_file.filename != '':         uploaded_file.save(uploaded_file.filename)     render redirect(url_for('index'))                  

The upload_file() function is busy with @app.route and so that it is invoked when the browser sends a POST request. Notation how the same root URL is split between two view functions, with index() set to accept the Get requests and upload_file() the POST ones.

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

The filename attribute in the FileStorage provides the filename submitted by the client. If the user submits the course without selecting a file in the file field, then the filename is going to exist an empty cord, and then it is important to always check the filename to make up one's mind if a file is bachelor or non.

When Flask receives a file submission information technology does not automatically write it to disk. This is actually a skilful thing, because it gives the application the opportunity to review and validate the file submission, as you will see later on. The bodily file data can exist accessed from the stream attribute. If the application just wants to salvage the file to disk, so it can telephone call the save() method, passing the desired path as an argument. If the file'south save() method is non called, then the file is discarded.

Want to test file uploads with this application? Make a directory for your application and write the code higher up every bit app.py. Then create a templates subdirectory, and write the HTML page from the previous section as templates/index.html. Create a virtual environment and install Flask on it, then run the application with flask run. Every time you lot submit a file, the server volition 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 above that you lot may find useful. Every bit I mentioned before, the file upload field tin exist configured to take multiple files. If you use request.files['file'] as above y'all will get only one of the submitted files, but with the getlist() method you tin access all of them in a for-loop:

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

Many people code their form handling routes in Flask using a single view function for both the Get and POST requests. A version of the instance application using a single view office could be coded as follows:

          @app.road('/', methods=['Get', 'POST']) def index():     if request.method == 'POST':         uploaded_file = request.files['file']         if uploaded_file.filename != '':             uploaded_file.save(uploaded_file.filename)         return redirect(url_for('index'))     render render_template('index.html')                  

Finally, if you employ the Flask-WTF extension to handle your forms, you lot can utilise the FileField object for your file uploads. The form used in the examples you lot've seen and so far tin 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')                  

Annotation that the FileField object comes from the flask_wtf package, unlike most other field classes, which are imported directly from the wtforms package. Flask-WTF provides ii 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 allowed extensions listing.

When yous use a Flask-WTF form, the data attribute of the file field object points to the FileStorage instance, so saving a file to disk works in the same way every bit in the examples higher up.

Securing file uploads

The file upload instance presented in the previous section is an extremely simplistic implementation that is not very robust. One of the most important rules in web evolution is that data submitted past clients should never be trusted, and for that reason when working with regular forms, an extension such as Flask-WTF performs strict validation of all fields before the course is accepted and the data incorporated into the application. For forms that include file fields there needs to be validation too, considering without file validation the server leaves the door open to attacks. For instance:

  • An assaulter can upload a file that is and so large that the deejay space in the server is completely filled, causing the server to malfunction.
  • An aggressor can arts and crafts an upload request that uses a filename such equally ../../../.bashrc or similar, with the endeavour to trick the server into rewriting system configuration files.
  • An attacker tin 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 utilise a configuration option provided by Flask. The MAX_CONTENT_LENGTH option controls the maximum size a request body can have. While this isn't an pick that is specific to file uploads, setting a maximum request body size effectively makes Flask discard whatever incoming requests that are larger than the allowed corporeality with a 413 status code.

Let'south modify the app.py example from the previous department to only have requests that are up to 1MB in size:

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

If you try to upload a file that is larger than 1MB, the application will now refuse information technology.

Validating filenames

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

A very uncomplicated validation to perform is to make certain that the file extension is one that the awarding is willing to accept, which is similar to what the FileAllowed validator does when using Flask-WTF. Let'due south say the application accepts images, and so it can configure the list of approved file extensions:

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

For every uploaded file, the application can make certain that the file extension is i of the immune ones:

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

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

In addition to the file extension, it is also of import to validate the filename, and any path given with it. If your awarding does non care nigh the filename provided past the customer, the most secure way to handle the upload is to ignore the client provided filename and generate your own filename instead, that you pass to the save() method. An example utilise case where this technique works well is with avatar image uploads. Each user's avatar can be saved with the user id as filename, then the filename provided by the client can be discarded. If your application uses Flask-Login, y'all could implement the following save() telephone call:

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

In other cases it may be better to preserve the filenames provided by the client, so the filename must be sanitized first. For those cases Werkzeug provides the secure_filename() function. Allow's see how this office 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 you see in the examples, no matter how complicated or malicious the filename is, the secure_filename() office reduces it to a flat filename.

Allow'due south contain secure_filename() into the example upload server, and also add a configuration variable that defines a dedicated location for file uploads. Here is the complete app.py source file with secure filenames:

          import os from flask import Flask, render_template, request, redirect, url_for, arrest 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 index():     return render_template('index.html')  @app.route('/', methods=['POST']) def upload_files():     uploaded_file = asking.files['file']     filename = secure_filename(uploaded_file.filename)     if filename != '':         file_ext = os.path.splitext(filename)[ane]         if file_ext non in app.config['UPLOAD_EXTENSIONS']:             abort(400)         uploaded_file.save(bone.path.join(app.config['UPLOAD_PATH'], filename))     return redirect(url_for('index'))                  

Validating file contents

The 3rd layer of validation that I'm going to talk over is the near complex. If your application accepts uploads of a certain file type, it should ideally perform some grade of content validation and reject any files that are of a different type.

How y'all reach content validation largely depends on the file types your application accepts. For the example application in this article I'thou using images, so I can employ the imghdr parcel from the Python standard library to validate that the header of the file is, in fact, an epitome.

Permit'south write a validate_image() function 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     render '.' + (format if format != 'jpeg' else 'jpg')                  

This function takes a byte stream as an statement. Information technology starts by reading 512 bytes from the stream, and and so resetting the stream pointer back, considering later when the save() role is called nosotros desire it to see the entire stream. The first 512 bytes of the image data are going to be sufficient to place the format of the image.

The imghdr.what() function tin can wait at a file stored on disk if the showtime argument is the filename, or else it can look at data stored in memory if the first argument is None and the data is passed in the 2d argument. The FileStorage object gives us a stream, so the most convenient pick is to read a safe corporeality of data from it and pass information technology as a byte sequence in the 2d statement.

The return value of imghdr.what() is the detected image format. The function supports a diverseness of formats, among them the popular jpeg, png and gif. If non known image format is detected, so the return value is None. If a format is detected, the name of the format is returned. The almost user-friendly is to return the format as a file extension, because the application tin then ensure that the detected extension matches the file extension, and so the validate_image() function converts the detected format into a file extension. This is as simple every bit adding a dot as prefix for all image formats except jpeg, which normally uses the .jpg extension, and so this case is treated as an exception.

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

          import imghdr import os from flask import Flask, render_template, request, redirect, url_for, arrest 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=['Mail']) 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 non in app.config['UPLOAD_EXTENSIONS'] or \                 file_ext != validate_image(uploaded_file.stream):             abort(400)         uploaded_file.save(os.path.bring together(app.config['UPLOAD_PATH'], filename))     return redirect(url_for('index'))                  

The just change in the view function to incorporate this last validation logic is here:

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

This expanded bank check first makes certain that the file extension is in the immune list, and then ensures that the detected file extension from looking at the information stream is the same as the file extension.

Earlier you exam this version of the application create a directory named uploads (or the path that you lot defined in the UPLOAD_PATH configuration variable, if unlike) so that files tin be saved at that place.

Using Uploaded Files

Y'all now know how to handle file uploads. For some applications this is all that is needed, equally the files are used for some internal process. But for a large number of applications, in particular those with social features such as avatars, the files that are uploaded past users take to be integrated with the application. Using the case of avatars, in one case a user uploads their avatar image, whatsoever mention of the username requires the uploaded prototype to appear to the side.

I divide file uploads into two large groups, depending on whether the files uploaded by users are intended for public use, or they are private to each user. The avatar images discussed several times in this commodity are clearly in the first group, as these avatars are intended to exist publicly shared with other users. On the other side, an awarding that performs editing operations on uploaded images would probably be in the second group, because yous'd want each user to simply have access to their own images.

Consuming public uploads

When images are of a public nature, the easiest way to make the images available for employ past the application is to put the upload directory inside the application'south static folder. For instance, an avatars subdirectory can be created inside static, and then avatar images tin exist saved in that location using the user id as name.

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

          uploaded_file.save(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 tin can be saved to a directory outside of the static folder, and and so a new route tin can be added to serve them. In the example app.py awarding file uploads are saved to the location set in the UPLOAD_PATH configuration variable. To serve these files from that location, we can implement the following road:

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

One advantage that this solution has over storing uploads inside the static binder is that hither you tin can implement additional restrictions before these files are returned, either directly with Python logic within the body of the function, or with decorators. For example, if yous desire to only provide access to the uploads to logged in users, you tin add together Flask-Login'south @login_required decorator to this road, or any other authentication or office checking machinery that y'all use for your normal routes.

Permit's use this implementation thought to show uploaded files in our example application. Here is a new complete version of app.py:

          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'] = 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 check     stream.seek(0)  # reset stream arrow     format = imghdr.what(None, header)     if not format:         return None     return '.' + (format if format != 'jpeg' else 'jpg')  @app.road('/') def index():     files = os.listdir(app.config['UPLOAD_PATH'])     render render_template('index.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 = 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('index'))  @app.route('/uploads/<filename>') def upload(filename):     return send_from_directory(app.config['UPLOAD_PATH'], filename)                  

In addition to the new upload() role, the index() view function gets the listing of files in the upload location using bone.listdir() and sends it down to the template for rendering. The index.html template updated to show uploads is shown below:

          <!doctype html> <html>   <caput>     <title>File Upload</title>   </head>   <body>     <h1>File Upload</h1>     <grade method="Mail service" action="" enctype="multipart/form-data">       <p><input type="file" name="file"></p>       <p><input blazon="submit" value="Submit"></p>     </form>     <60 minutes>     {% 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 paradigm, a thumbnail is added at the bottom of the folio:

Basic file upload form

Consuming individual uploads

When users upload private files to the application, boosted checks need to be in place to prevent sharing files from one user with unauthorized parties. The solution for these cases crave variations of the upload() view function shown in a higher place, with additional admission checks.

A common requirement is to only share uploaded files with their owner. A convenient mode to store uploads when this requirement is present is to use a separate directory for each user. For case, uploads for a given user tin can be saved to the uploads/<user_id> directory, and and so the uploads() role can be modified to only serve uploads from the user's ain upload directory, making information technology impossible for one user to see files from some other. Below yous can come across a possible implementation of this technique, once over again assuming Flask-Login is used:

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

Showing upload progress

Upwardly until now we accept relied on the native file upload widget provided by the web browser to initiate our file uploads. I'1000 sure nosotros can all agree that this widget is not very appealing. Not but that, simply the lack of an upload progress display makes it unusable for uploads of large files, as the user receives no feedback during the unabridged upload process. While the scope of this commodity is to cover the server side, I thought information technology would be useful to give you a few ideas on how to implement a modernistic JavaScript-based file upload widget that displays upload progress.

The expert news is that on the server in that location aren't any big changes needed, the upload mechanism works in the same way regardless of what method y'all use in the browser to initiate the upload. To show you an example implementation I'thousand going to supplant the HTML grade in alphabetize.html with one that is compatible with dropzone.js, a popular file upload client.

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

          <html>   <head>     <championship>File Upload</title>     <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/dropzone/v.7.one/min/dropzone.min.css">   </head>   <body>     <h1>File Upload</h1>     <form action="{{ url_for('upload_files') }}" class="dropzone">     </class>     <script src="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.7.1/min/dropzone.min.js"></script>   </body> </html>                  

The ane interesting thing that I've found when implementing dropzone is that it requires the action attribute in the <class> chemical element to be set, even though normal forms accept an empty action to indicate 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's basically it! Yous tin can at present driblet files and they'll exist uploaded to the server with a progress bar and a last indication of success or failure.

If the file upload fails, either due to the file being too large or invalid, dropzone wants to brandish an error message. Because our server is currently returning the standard Flask error pages for the 413 and 400 errors, y'all will see some HTML gibberish in the error popup. To right this we tin can update the server to return its error responses as text.

The 413 error for the file too large condition is generated by Flask when the asking payload is bigger than the size gear up in the configuration. To override the default error folio we accept to use the app.errorhandler decorator:

          @app.errorhandler(413) def too_large(due east):     return "File is as well large", 413                  

The second fault condition is generated by the application when any of the validation checks fails. In this case the fault was generated with a abort(400) call. Instead of that the response can exist generated straight:

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

The final change that I'm going to make isn't really necessary, but information technology saves a bit of bandwidth. For a successful upload the server returned a redirect() back to the principal route. This caused the upload class to be displayed again, and also to refresh the listing of upload thumbnails at the bottom of the page. None of that is necessary now because the uploads are done as background requests by dropzone, so we can eliminate that redirect and switch to an empty response with a lawmaking 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, abort, \     send_from_directory from werkzeug.utils import secure_filename  app = Flask(__name__) app.config['MAX_CONTENT_LENGTH'] = two * 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 too large", 413  @app.route('/') def index():     files = bone.listdir(app.config['UPLOAD_PATH'])     return render_template('alphabetize.html', files=files)  @app.route('/', 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'] or \                 file_ext != validate_image(uploaded_file.stream):             return "Invalid image", 400         uploaded_file.save(os.path.bring together(app.config['UPLOAD_PATH'], filename))     return '', 204  @app.route('/uploads/<filename>') def upload(filename):     return send_from_directory(app.config['UPLOAD_PATH'], filename)                  

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

Basic file upload form

The dropzone.js library is very flexible and has many options for customization, then I encourage you to visit their documentation to learn how to adapt information technology to your needs. Yous tin can besides await for other JavaScript file upload libraries, as they all follow the HTTP standard, which means that your Flask server is going to piece of work well with all of them.

Conclusion

This was a long overdue topic for me, I tin't believe I accept never written anything on file uploads! I'd honey you hear what you recollect about this topic, and if you think there are aspects of this characteristic that I haven't covered in this article. Experience free to allow me know beneath in the comments!

fontainepottirldind.blogspot.com

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

0 Response to "Flask Upload Image and Display Same Page"

Post a Comment

Iklan Atas Artikel

Iklan Tengah Artikel 1

Iklan Tengah Artikel 2

Iklan Bawah Artikel