How To Upload And Save Image Flask
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 spider web framework Flask fully supports it, but there are many implementation details that fall 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 confusion and dubiety.
In this article I'one thousand going to bear witness 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 also as the cool JavaScript-based upload widgets:
A Basic File Upload Form
From a high-level perspective, a client uploading a file is treated the same equally any other class information submission. In other words, you have to define an HTML form with a file field in it.
Here is a simple HTML page with a form that accepts a file:
<!doctype html> <html> <head> <championship>File Upload</title> </head> <body> <h1>File Upload</h1> <grade method="Post" action="" enctype="multipart/form-information"> <p><input type="file" proper name="file"></p> <p><input type="submit" value="Submit"></p> </form> </body> </html>
As you probably know, the method
attribute of the <form>
chemical element can be Get
or POST
. With Get
, the data is submitted in the query string of the asking URL, while with POST
it goes in the request body. When files are existence included in the form, you lot must use POST
, as information technology would be impossible to submit file information in the query cord.
The enctype
attribute in the <grade>
element is normally not included with forms that don't accept files. This attribute defines how the browser should format the data earlier it is submitted to the server. The HTML specification defines three possible values for it:
-
application/x-www-course-urlencoded
: This is the default, and the all-time format for whatever forms except those that incorporate file fields. -
multipart/form-data
: This format is required when at least 1 of the fields in the form is a file field. -
text/plain
: This format has no practical employ, then you lot should ignore it.
The actual file field is the standard <input>
element that we employ for most other form fields, with the type set to file
. In the example above I haven't included any boosted attributes, only the file field supports two that are sometimes useful:
-
multiple
can exist used to allow multiple files to be uploaded in a unmarried file field. Example:
<input type="file" name="file" multiple>
-
have
can be used to filter the allowed file types that tin can be selected, either past file extension or by media type. Examples:
<input type="file" name="doc_file" accept=".doc,.docx"> <input type="file" name="image_file" accept="paradigm/*">
Accepting File Submissions with Flask
For regular forms, Flask provides access to submitted form fields in the asking.form
dictionary. File fields, withal, are included in the asking.files
dictionary. The request.course
and request.files
dictionaries are actually "multi-dicts", a specialized dictionary implementation that supports duplicate keys. This is necessary because forms can include multiple fields with the same name, every bit is oft the example with groups of check boxes. This besides happens with file fields that allow multiple files.
Ignoring of import aspects such every bit validation and security for the moment, the short Flask application shown below accepts a file uploaded with the form 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.road('/') def index(): render render_template('alphabetize.html') @app.route('/', methods=['Mail']) def upload_file(): uploaded_file = asking.files['file'] if uploaded_file.filename != '': uploaded_file.relieve(uploaded_file.filename) return redirect(url_for('alphabetize'))
The upload_file()
function is decorated with @app.route
then that information technology is invoked when the browser sends a POST
request. Note how the same root URL is divide betwixt 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 course FileStorage, which Flask imports from Werkzeug.
The filename
attribute in the FileStorage
provides the filename submitted past the client. If the user submits the course without selecting a file in the file field, so the filename is going to be an empty string, so information technology is important to always check the filename to determine if a file is available or not.
When Flask receives a file submission it does not automatically write it to disk. This is really a skilful thing, because it gives the application the opportunity to review and validate the file submission, every bit y'all will come across later. The actual file data can be accessed from the stream
aspect. If the application just wants to salvage the file to disk, and then it can call the save()
method, passing the desired path every bit an argument. If the file's salve()
method is not called, and then the file is discarded.
Want to test file uploads with this application? Make a directory for your application and write the lawmaking above as app.py. Then create a templates subdirectory, and write the HTML page from the previous department as templates/alphabetize.html. Create a virtual surround and install Flask on it, then run the application with flask run
. Every time y'all submit a file, the server will write a copy of it in the electric current directory.
Before I move on to the topic of security, I'k going to discuss a few variations on the code shown above that y'all may detect useful. As I mentioned before, the file upload field can be configured to accept multiple files. If y'all apply request.files['file']
as to a higher place you will get simply one of the submitted files, but with the getlist()
method yous 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 class handling routes in Flask using a single view part for both the GET
and POST
requests. A version of the example application using a single view office could be coded as follows:
@app.route('/', methods=['GET', 'POST']) def alphabetize(): if request.method == 'Mail': uploaded_file = request.files['file'] if uploaded_file.filename != '': uploaded_file.salvage(uploaded_file.filename) return redirect(url_for('index')) return render_template('index.html')
Finally, if you employ the Flask-WTF extension to handle your forms, you lot tin can use the FileField
object for your file uploads. The form used in the examples you've seen so far tin be written using Flask-WTF as follows:
from flask_wtf import FlaskForm from flask_wtf.file import FileField from wtforms import SubmitField course MyForm(FlaskForm): file = FileField('File') submit = SubmitField('Submit')
Annotation that the FileField
object comes from the flask_wtf
bundle, unlike most other field classes, which are imported directly from the wtforms
package. Flask-WTF provides 2 validators for file fields, FileRequired
, which performs a check like to the empty string bank check, and FileAllowed
, which ensures the file extension is included in an immune extensions list.
When you 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 equally in the examples to a higher place.
Securing file uploads
The file upload case presented in the previous department is an extremely simplistic implementation that is non very robust. One of the near important rules in web development is that information 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 be validation also, because without file validation the server leaves the door open up to attacks. For example:
- An attacker can upload a file that is so big that the disk space in the server is completely filled, causing the server to malfunction.
- An assailant tin craft an upload asking that uses a filename such every bit ../../../.bashrc or similar, with the attempt to play a trick on the server into rewriting system configuration files.
- An attacker can upload files with viruses or other types of malware in a identify where the application, for example, expects images.
Limiting the size of uploaded files
To prevent clients from uploading very big files, you tin can utilise a configuration option provided by Flask. The MAX_CONTENT_LENGTH
selection controls the maximum size a asking body can have. While this isn't an selection that is specific to file uploads, setting a maximum request body size finer makes Flask discard any incoming requests that are larger than the immune corporeality with a 413 status code.
Let'southward change the app.py example from the previous section to but accept requests that are upwardly to 1MB in size:
app.config['MAX_CONTENT_LENGTH'] = 1024 * 1024
If you attempt to upload a file that is larger than 1MB, the application will now pass up it.
Validating filenames
We tin't really trust that the filenames provided by the client are valid and safe to use, then filenames coming with uploaded files have to exist validated.
A very uncomplicated validation to perform is to make sure that the file extension is one that the application is willing to take, which is similar to what the FileAllowed
validator does when using Flask-WTF. Let's say the awarding accepts images, so it can configure the list of approved file extensions:
app.config['UPLOAD_EXTENSIONS'] = ['.jpg', '.png', '.gif']
For every uploaded file, the application tin can brand certain 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, any filenames that practise non have one of the approved file extensions is going to be responded with a 400 error.
In improver to the file extension, it is also important to validate the filename, and any path given with it. If your application does non care about 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 laissez passer to the relieve()
method. An case use case where this technique works well is with avatar image uploads. Each user's avatar tin be saved with the user id as filename, and so the filename provided past the client can be discarded. If your application uses Flask-Login, you could implement the following save()
call:
uploaded_file.salvage(bone.path.bring together('static/avatars', current_user.get_id()))
In other cases it may be amend to preserve the filenames provided by the client, so the filename must exist sanitized first. For those cases Werkzeug provides the secure_filename() function. Let's see how this part 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'
Every bit you lot see in the examples, no thing how complicated or malicious the filename is, the secure_filename()
function reduces it to a flat filename.
Permit'southward contain secure_filename()
into the example upload server, and also add a configuration variable that defines a defended location for file uploads. Hither is the consummate app.py source file with secure filenames:
import bone from flask import Flask, render_template, request, 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.road('/') def index(): return render_template('index.html') @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)[i] if file_ext not in app.config['UPLOAD_EXTENSIONS']: abort(400) uploaded_file.relieve(os.path.bring together(app.config['UPLOAD_PATH'], filename)) return redirect(url_for('index'))
Validating file contents
The third layer of validation that I'm going to talk over is the most circuitous. If your awarding accepts uploads of a sure file type, it should ideally perform some form of content validation and reject any files that are of a dissimilar type.
How you reach content validation largely depends on the file types your application accepts. For the example application in this article I'yard using images, so I can use the imghdr packet from the Python standard library to validate that the header of the file is, in fact, an image.
Let'south write a validate_image()
role 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 non format: return None render '.' + (format if format != 'jpeg' else 'jpg')
This function takes a byte stream as an statement. It starts by reading 512 bytes from the stream, and so resetting the stream pointer dorsum, because subsequently when the salvage()
function is chosen we desire it to come across the entire stream. The start 512 bytes of the image data are going to exist sufficient to place the format of the paradigm.
The imghdr.what()
part can wait at a file stored on disk if the first argument is the filename, or else information technology tin can wait at data stored in memory if the commencement argument is None
and the information is passed in the 2nd argument. The FileStorage
object gives united states a stream, so the nearly convenient selection is to read a safe amount of data from information technology and pass information technology as a byte sequence in the second argument.
The return value of imghdr.what()
is the detected image format. The function supports a variety of formats, amid them the popular jpeg
, png
and gif
. If not known image format is detected, so 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 every bit a file extension, because the application can then ensure that the detected extension matches the file extension, so the validate_image()
function converts the detected format into a file extension. This is every bit simple as adding a dot as prefix for all image formats except jpeg
, which ordinarily uses the .jpg
extension, 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, asking, 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.road('/') def index(): render render_template('index.html') @app.route('/', methods=['Mail service']) def upload_files(): uploaded_file = request.files['file'] filename = secure_filename(uploaded_file.filename) if filename != '': file_ext = bone.path.splitext(filename)[one] 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)) render redirect(url_for('index'))
The merely modify in the view function to comprise this last validation logic is here:
if file_ext non in app.config['UPLOAD_EXTENSIONS'] or \ file_ext != validate_image(uploaded_file.stream): arrest(400)
This expanded check first makes sure that the file extension is in the allowed list, and so ensures that the detected file extension from looking at the data stream is the same as the file extension.
Before you test this version of the application 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
Yous at present know how to handle file uploads. For some applications this is all that is needed, equally the files are used for some internal procedure. But for a big 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 example of avatars, one time a user uploads their avatar epitome, any mention of the username requires the uploaded epitome to announced to the side.
I carve up file uploads into two big groups, depending on whether the files uploaded by users are intended for public use, or they are individual to each user. The avatar images discussed several times in this article are clearly in the start group, as these avatars are intended to be publicly shared with other users. On the other side, an application that performs editing operations on uploaded images would probably be in the second group, because you'd want each user to only have access to their own images.
Consuming public uploads
When images are of a public nature, the easiest manner to brand the images available for use by the application is to put the upload directory within the application's static folder. For example, an avatars subdirectory can exist created within static, and then avatar images tin can be saved in that location using the user id every bit proper noun.
Referencing these uploads stored in a subdirectory of the static folder is washed in the same way as regular static files of the application, using the url_for()
function. I previously suggested using the user id as a filename, when saving an uploaded avatar image. This was the fashion the images were saved:
uploaded_file.save(bone.path.bring together('static/avatars', current_user.get_id()))
With this implementation, given a user_id
, the URL for the user'southward avatar can exist 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 road 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 tin 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 within the static binder is that here you can implement additional restrictions before these files are returned, either straight with Python logic inside the body of the role, or with decorators. For instance, if you desire to simply provide access to the uploads to logged in users, yous can add Flask-Login's @login_required
decorator to this route, or any other authentication or office checking mechanism that you use for your normal routes.
Allow'due south use this implementation idea 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 be enough for a header check stream.seek(0) # reset stream pointer format = imghdr.what(None, header) if not format: return None return '.' + (format if format != 'jpeg' else 'jpg') @app.route('/') 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 = 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.salve(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 addition to the new upload()
function, the index()
view part gets the list of files in the upload location using bone.listdir()
and sends it down to the template for rendering. The alphabetize.html template updated to testify uploads is shown below:
<!doctype html> <html> <head> <title>File Upload</championship> </head> <body> <h1>File Upload</h1> <form method="Mail service" activeness="" enctype="multipart/form-data"> <p><input type="file" name="file"></p> <p><input type="submit" value="Submit"></p> </class> <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 image, a thumbnail is added at the bottom of the page:
Consuming private uploads
When users upload private files to the awarding, additional checks need to be in identify to forestall sharing files from one user with unauthorized parties. The solution for these cases require variations of the upload()
view part shown in a higher place, with additional access checks.
A common requirement is to only share uploaded files with their owner. A user-friendly way to store uploads when this requirement is present is to use a separate directory for each user. For example, uploads for a given user can be saved to the uploads/<user_id>
directory, and and so the uploads()
function tin be modified to only serve uploads from the user's own upload directory, making it impossible for one user to run across files from another. Below y'all tin can see a possible implementation of this technique, once again assuming Flask-Login is used:
@app.route('/uploads/<filename>') @login_required def upload(filename): return send_from_directory(os.path.bring together( app.config['UPLOAD_PATH'], current_user.get_id()), filename)
Showing upload progress
Up until now we accept relied on the native file upload widget provided by the spider web browser to initiate our file uploads. I'one thousand sure we can all agree that this widget is not very appealing. Non merely 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 entire upload process. While the scope of this article is to cover the server side, I thought it would be useful to give you a few ideas on how to implement a modern JavaScript-based file upload widget that displays upload progress.
The skillful 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 you lot utilize in the browser to initiate the upload. To show you an case implementation I'thou going to replace the HTML form in index.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 according to the dropzone documentation:
<html> <head> <title>File Upload</title> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.7.1/min/dropzone.min.css"> </head> <body> <h1>File Upload</h1> <form action="{{ url_for('upload_files') }}" course="dropzone"> </grade> <script src="https://cdnjs.cloudflare.com/ajax/libs/dropzone/5.7.ane/min/dropzone.min.js"></script> </torso> </html>
The i interesting affair that I've plant when implementing dropzone is that it requires the activeness
attribute in the <form>
element to be set, even though normal forms accept an empty activity to indicate that the submission goes to the same URL.
Offset the server with this new version of the template, and this is what you'll get:
That's basically it! Yous can at present drop files and they'll be uploaded to the server with a progress bar and a concluding indication of success or failure.
If the file upload fails, either due to the file being besides big or invalid, dropzone wants to display an error message. Because our server is currently returning the standard Flask mistake pages for the 413 and 400 errors, you lot will meet some HTML gibberish in the error popup. To correct this we 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 request payload is bigger than the size fix in the configuration. To override the default error page we have to use the app.errorhandler
decorator:
@app.errorhandler(413) def too_large(e): return "File is too large", 413
The second fault condition is generated past the application when whatever of the validation checks fails. In this example the fault was generated with a arrest(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): render "Invalid image", 400
The final modify that I'm going to brand isn't really necessary, but it saves a bit of bandwidth. For a successful upload the server returned a redirect()
back to the main route. This acquired the upload form to be displayed again, and also to refresh the list of upload thumbnails at the lesser of the folio. None of that is necessary now because the uploads are washed as background requests by dropzone, so we can eliminate that redirect and switch to an empty response with a code 204.
Here is the consummate 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'] = 2 * 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(e): return "File is too large", 413 @app.road('/') def alphabetize(): files = os.listdir(app.config['UPLOAD_PATH']) return render_template('index.html', files=files) @app.route('/', 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): 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 volition take a proper message:
The dropzone.js library is very flexible and has many options for customization, so I encourage you to visit their documentation to learn how to adapt it to your needs. You can also expect for other JavaScript file upload libraries, as they all follow the HTTP standard, which ways that your Flask server is going to work well with all of them.
Decision
This was a long overdue topic for me, I can't believe I have never written anything on file uploads! I'd love you hear what yous call up about this topic, and if you remember at that place are aspects of this feature that I haven't covered in this commodity. Feel costless to allow me know below in the comments!
Source: https://blog.miguelgrinberg.com/post/handling-file-uploads-with-flask
Posted by: binghamfallsocring.blogspot.com
0 Response to "How To Upload And Save Image Flask"
Post a Comment