Handling File Upload

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

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.

 

Leave a Reply

Your email address will not be published. Required fields are marked *