How to save images directly to server with summernote and Flask

Tutorial

September 12, 2019

Summernote is a "super simple WYSIWYG editor on bootstrap". I got to use it recently for a client's website, and I can say I liked it, it was a breeze to set up and get it to work; easy to customize as well. There was just one problem. By default, Summernote saves images in base64, which is not necessarily a bad thing, but for this particular case I needed to store the images directly on the server.

I based my solution on a stack overflow answer and Flask's official docs on how to upload files. Now, I'm a huge Flask fan but I gotta admit this is easier (in theory, I haven't actually tried) with Django as it has a Summernote extension which I think you can configure to save images directly to the server.

Javascript

First thing is to actually initialize the Summernote editor


        $('#summernote').summernote({
            placeholder: 'Write here',
            tabsize: 2,
            height: 500,
            callbacks: {
                onImageUpload: function(image) {
                    uploadImage(image[0]);
                }
            }
        });
        

This is just the basic set up of the editor, the only thing you should pay attention to is the callback part. Everytime you upload an image, the onImageUpload function will be called. By default this saves the images a base64 string, so what we are doing here is overwriting that and calling our own function: uploadImage.

Our uploadImage function looks like this:


        function uploadImage(image) {
            var data = new FormData();
            data.append("image", image);
            $.ajax({
                url: "/my_flask_endpoint",
                cache: false,
                contentType: false,
                processData: false,
                data: data,
                type: "POST",
                success: function(filename) {
                    var image = $('<img>').attr('src', '/route/to/images/' + filename).addClass("img-fluid");
                    $('#summernote').summernote("insertNode", image[0]);
                },
                error: function(data) {
                    console.log(data);
                }
            });
        }
        

Each time we insert an image using the editor it calls our custom function, which at the same time makes an AJAX call to one of our Flask's routes, my_flask_endpoint. If the AJAX call is successfull, then we create the img HTML tag using the image's filename (returned from our Flask route).

With some minor modifications you can use this to save the image to an external storage, like AWS' S3

Python

We now have to set our my_flask_endpoint route to actually save the image.


        @app.route("/my_flask_endpoint", methods=["GET", "POST"])
        def _my_flask_endpoint():
            if request.method == "POST":
                file = request.files["image"]
                if file.filename == '':
                    return "error.png"
                if file and allowed_file(file.filename): 
                    if os.path.exists(app.config["UPLOAD_FOLDER"] + "/" + file.filename): # if image with same name exists
                        _dot = file.filename.find(".")
                        file.filename = file.filename[:_dot] + str(uuid.uuid4()) + file.filename[_dot:]
                    filename = secure_filename(file.filename)
                    file.save(os.path.normpath(os.path.join(app.config["UPLOAD_FOLDER"], filename)))
                    return file.filename
        

The python code is pretty straight forward. First we check if the request type was POST, if it is, then we get the actual image with file = request.files["image"]. If for some reason its filename is an empty string, then we return "error.png", which can be an already existing image that would appear in the summernote editor telling the user there was a problem with their file.

Next step is to verify that the file exists, and if it is allowed. The allowed_file function is given by Flask's official docs:


        ALLOWED_EXTENSIONS = set(["png", "jpg", "jpeg", "gif"])

        def allowed_file(filename):
            return '.' in filename and \
                filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
        

The next part is to check if an image with the same name already exists in the specified location. If it does, then we only modify the filename by adding a random string using uuid. If you don't want to save the same image twice, you can return the same "error.png" image to tell the user to upload an image with another name:


        if os.path.exists(app.config["UPLOAD_FOLDER"] + "/" + file.filename):
            return "error.png"
        

Lastly, we secure the file by importing and using secure_filename from werkzeug.utils, save the image, and return its name.

That's it! Everytime the user inserts an image it should be visible from the editor and saved to the server. I hope this was useful.