Forums

Download a PIL image with Flask

Hello, I created a website that modified the image with Pillow and then downloaded it. Locally these two codes work perfectly but with pythonanywhere the image is not downloaded. I need help. Here are the two possible codes :

flask_app.py

from flask import Flask, request, redirect, url_for, render_template, send_file
from werkzeug import secure_filename
from PIL import Image
import os

app = Flask(__name__)

@app.route('/', methods=['GET', 'POST'])
def upload():
    return render_template('upload.html')

@app.route('/download/', methods=['POST'])
def download():

    try:
        image = request.files['image']
        nom_image = secure_filename(image.filename)
        image = Image.open(image)
        ...
        image.save('/home/modificateurimage/mysite/static/images/'+nom_image)
        return send_file('/home/modificateurimage/mysite/static/images/'+nom_image, mimetype='image/jpeg', attachment_filename=nom_image, as_attachment=True), os.remove('/home/modificateurimage/mysite/static/images/'+nom_image)

    except Exception as e:
        print(e)
        return redirect(url_for('upload'))

OR flask_app.py

from flask import Flask, request, redirect, url_for, render_template, send_file
from werkzeug import secure_filename
from PIL import Image
from io import BytesIO

app = Flask(__name__)

@app.route('/', methods=['GET', 'POST'])
def upload():
    return render_template('upload.html')

@app.route('/download/', methods=['POST'])
def download():

    try:
        image = request.files['image']
        nom_image = secure_filename(image.filename)
        image = Image.open(image)
        ...
        img_io = BytesIO()
        image.save(img_io, extension.upper(), quality=70)
        img_io.seek(0)
        return send_file(img_io, mimetype='image/jpeg', attachment_filename=nom_image ,as_attachment=True)

    except Exception as e:
        print(e)
        return redirect(url_for('upload'))

does the image get saved in the first case? and have you checked both your server logs and your error logs?

Thanks for your answer. For both cases I don't have any errors when it can't be downloaded. For the first case it works when I remove the

, os.remove('/home/modificateurimage/mysite/static/images/'+nom_image)

from the return function. The problem is that the file is not removed from the folder.

what is the os.remove? why is it part of the send_file call?

First I save the image, then I send it and finally I delete it from the folder using os.remove. The os.remove is not part of the send_file. They are on the same line, in the return of the function.

The remove cannot be there. First, it returns a tuple, which is not a valid return type for a flask view. Second, the remove is executed before the return, so the file has been deleted before it can be served. Remove the os.remove from that code and delete the file at some other point (perhaps a clean-up process in a scheduled task)

The os.remove being after the send_file, the file should be deleted after sending. I can't put the os.remove on a next line because the return send_file stops the function.

OK, so just to make sure I understand what you're trying to achieve -- you want to use send_file to send the file back to the browser, and then after that has completed, you want to remove the file. Is that correct?

If so, how about:

resp = send_file(img_io, mimetype='image/jpeg', attachment_filename=nom_image ,as_attachment=True)
os.remove('/home/modificateurimage/mysite/static/images/'+nom_image)
return resp

...? I've checked with the code below, and it seems to work fine. That said, I would be a little worried that the reading of the file using send_file might happen asynchronously under some circumstances, and so the removal would happen before the file was sent.

import os

from flask import Flask, send_file


app = Flask(__name__)

@app.route('/')
def hello_world():
    resp = send_file("/home/giles/foo.txt")
    os.remove("/home/giles/foo.txt")
    return resp

Thank you very much ! It works perfectly for me too. How can I run both resp and redirect(url_for('upload')) at the end of the function to send the image and reload the page ? It doesn't work when I do this :

resp = send_file(img_io, mimetype='image/jpeg', attachment_filename=nom_image ,as_attachment=True)
os.remove('/home/modificateurimage/mysite/static/images/'+nom_image)
return resp, redirect(url_for('upload'))

Or even this :

resp = send_file(img_io, mimetype='image/jpeg', attachment_filename=nom_image ,as_attachment=True)
os.remove('/home/modificateurimage/mysite/static/images/'+nom_image)
redirect(url_for('upload'))
return resp

A Flask view can only return one response -- that's just the way HTTP works, one request from the browser leads to one response from the server.

As far as I'm aware, the only way to achieve the effect you're looking for would be to return a page with embedded JavaScript which triggered the file download, then moved on to the other page. This page has some sample code that you could adapt.

upload.html <!DOCTYPE html> <html>

<head>
    <meta charset="utf-8" />
    <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}">
    <script src="{{ url_for('static', filename='loading.js') }}"></script>
    <script src="http://code.jquery.com/jquery.js"></script>
    <title>Image</title>
</head>

<body>
    <form action="{{ url_for('download') }}" method="post" enctype=multipart/form-data>

        <div id="loading">
            <center><p class="titre">Please wait</p></center>
            <br>
            <center><img src="{{ url_for('static', filename='loadingimage.gif') }}" width="20%" height="20%"></center>
        </div>

        <div id="content">
            ...
            <input type="submit" value="Send" id="submit_button" onclick="loading();">
        </div>

    </form>
</body>

</html>

loading.js

function loading() {
    console.log("loading");
    $("#loading").show();
    $("#content").hide();       
};

function stoploading() {
    console.log("stoploading");
    $("#content").show();
    $("#loading").hide();       
};

How can I run stoploading() when the image is sent ? I think it's from the python but I don't know how to do it.

When a page is returned from the form POST, then the browser will move on to the next page -- unfortunately there's no way you can trigger code when that happens. (There used to be, but it it was abused by ad companies, so the browsers removed it.)

If you want JavaScript code to be executed after a request, then you'll need to make the request from JavaScript and then handle the response there too.

But I can call the JavaScript stoploading() function from Python. I could put it on before the return. Couldn't I?

No, you can't call a Javascript function in your page from Python on the server.

flask_app.py

from flask import Flask, request, redirect, url_for, render_template, send_file
from werkzeug import secure_filename
from PIL import Image
from io import BytesIO
import json

app = Flask(__name__)

@app.route('/', methods=['GET', 'POST'])
def upload():
    return render_template('upload.html')

@app.route('/download/', methods=['POST'])
def download():

    try:
        image = request.files['image']
        nom_image = secure_filename(image.filename)
        image = Image.open(image)
        ...
        image.save(directory+nom_image)
        reponse = send_file(directory+nom_image, mimetype='image/jpeg', attachment_filename=nom_image, as_attachment=True)
        self.response.out.write('done')
        return reponse

    except Exception as e:
        print(e)
        return redirect(url_for('upload'))

loading.js

function loading() {
    import json
    while (true) {
        dynamic_data = 'not_done'
        var your_generated_table = $('table'),
        dynamic_data = JSON.parse(your_generated_table.attr('data-dynamic'));
        while (dynamic_data != 'done') {
            console.log("loading");
            $("#loading").show();
            $("#content").hide();
        }
        while (dynamic_data === 'done') {
            $("#content").show();
            $("#show").hide();
        }
    }
}

As you saw, I tried to send a variable from Python to JavaScript at the end of Python processing using json. However, this code does not work. Could you help me ? Thank you in advance.

When you say it doesn't work, what exactly happens? If you use the browser's development tools, what do you see being sent back from the Flask app?