Hello, welcome back to the 7th post of my Flask series, this post would be a continuation of my previous post, please click on any link below to view any post you missed
OUTLINE OF FLASK SERIES
- Structuring a Flask-Restful API for Production
- Creating Custom Error Pages and Handling Exceptions for Flask
- Flask authentication with JWT
- CRUD Operations with Flask
- Using Marshmallow to Simplify Parameter Validation in APIs.
- Email Setup, User Confirmation, and Password Reset Feature.
- Handling File Upload (this article)
At the end of this post, you will learn the following
- Build a user display picture.
- Validating files before saving the path to DB.
In the previous post, we completed the account opening workflow by activating the user accounts via email and password reset features.
To store the user profile, we will create a new attribute (display_image) in the User model. We are not going to store the image directly in the DB. Instead, we are going to store the image path to the database and save the image to an upload folder.
class User(BaseModel): __tablename__ = 'user' #============existing code display_image = db.Column(db.String(100), default=None) -#============existing code
Make migrations DB migrations
flask db migrate flask db upgrade
Add the following code to config.py, set a destination folder for the image, limit image size to 4MB, and set allowed image extension.
import os basedir = os.path.abspath(os.path.dirname(__file__)) class Config: #==========existing code UPLOAD_FOLDER = os.path.join(basedir, 'upload') ALLOWED_EXTENSIONS = set(['png', 'jpg', 'jpeg', 'gif','svg','bmp']) ALLOWED_MIMETYPES_EXTENSIONS = set(['image/apng','image/bmp','image /jpeg','image/png','image/svg+xml']) MAX_CONTENT_LENGTH = 4 * 1024 * 1024
Create a new resource UserDisplayPictureResource to handle our user image upload in the user.py file
import os import re from http import HTTPStatus from flask import current_app, render_template, request, url_for from flask_jwt_extended import ( create_access_token, create_refresh_token, get_jwt_identity, get_raw_jwt, jwt_optional, jwt_refresh_token_required, jwt_required, ) from flask_restful import Api, Resource from webargs import validate from webargs.fields import Email, Str from webargs.flaskparser import use_kwargs from werkzeug.datastructures import FileStorage from werkzeug.security import check_password_hash, generate_password_hash from werkzeug.utils import secure_filename from api.models import User api = Api() black_list = set() def allowed_file(filename): return ( "." in filename and filename.rsplit(".", 1)[1].lower() in current_app.config["ALLOWED_EXTENSIONS"] ) #===================existing code base class UserDisplayPictureResource(Resource): @jwt_required def put(self): if "file" not in request.files: return {"message": "no file"}, HTTPStatus.BAD_REQUEST uploaded_file = request.files["file"] # Check if the file is one of the allowed types/extensions if isinstance(uploaded_file, FileStorage) and allowed_file( uploaded_file.filename ): # Make the filename safe, remove unsupported chars filename = secure_filename(uploaded_file.filename) user = User.get_by_id(id=get_jwt_identity()) # for further security checks mimetype = uploaded_file.content_type if mimetype not in current_app.config["ALLOWED_MIMETYPES_EXTENSIONS"]: return ( {"message": "File type not allowed, upload png, jpeg, svg files"}, HTTPStatus.BAD_REQUEST, ) target = os.path.join( current_app.config["UPLOAD_FOLDER"], user.username, filename ) uploaded_file.save(target) user.display_image = target user.save() return {"msg": "uploaded image successfully"}, HTTPStatus.OK return ( {"message": "An error occured"}, HTTPStatus.BAD_REQUEST, )
The UPLOAD_FOLDER is where we will store the uploaded files.
ALLOWED_EXTENSIONS is the set of allowed file extensions.
This way we make sure that users are not able to upload HTML files that would cause XSS problems and also make sure to disallow .php files too.
Register your UserDisplayPictureResource, update your app.py file
from resources.user import (RefreshAccessTokenResource, RevokeAccessTokenResource, UserInfoResource, UserLoginResource, UserRegistrationResource, black_list,UserDisplayPictureResource) ##======================existing codebase # register our URLs for user module ##==================existing codebase api.add_resource( UserDisplayPictureResource, "/v1/user/display_image/", endpoint="display_image" )
This is the end of our tutorial, thanks for visiting my blog.
Exercise for the readers
Alternatively, you can update the UserInfoResource and serve the user display image from the location using the send_from_directory method.
from flask import send_from_directory def upload(filename): return send_from_directory(app.config['UPLOAD_FOLDER'], filename)
We have come to the end of our tutorial.
The link to Github tutorial is here
Until then happy coding.