112

I'm trying to create a web app with Flask that lets a user upload a file and serve them to another user. Right now, I can upload the file to the upload_folder correctly. But I can't seem to find a way to let the user download it back.

I'm storing the name of the filename into a database.

I have a view serving the database objects. I can delete them too.

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

    problemes = Probleme.query.all()

    if 'user' not in session:
        return redirect(url_for('login'))

    if request.method == 'POST':
        delete = Probleme.query.filter_by(id=request.form['del_button']).first()
        db.session.delete(delete)
        db.session.commit()
        return redirect(url_for('dashboard'))

    return render_template('dashboard.html', problemes=problemes)

In my HTML I have:

<td><a href="{{ url_for('download', filename=probleme.facture) }}">Facture</a></td>

and a download view :

@app.route('/uploads/<path:filename>', methods=['GET', 'POST'])
def download(filename):
    return send_from_directory(directory=app.config['UPLOAD_FOLDER'], filename=filename)

But it's returning :

Not Found

The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.

I just want to link the filename to the object and let the user download it (For every object in the same view)

EternalObserver
  • 517
  • 7
  • 19
Saimu
  • 1,254
  • 2
  • 9
  • 11
  • 1
    For what URL does Flask return a 404 Not Found response? Have you verified that the filename in question really resides in `app.config['UPLOAD_FOLDER']`? – Martijn Pieters Jul 04 '14 at 15:26
  • It does, because I have an upload forms that really did upload the file. I just can't download it. The uploading to the folder is fine. The url is : http://127.0.0.1:5000/uploads/rsc_chapitre17.pdf and the file is really there ! – Saimu Jul 04 '14 at 15:27
  • 1
    The reason I ask is because `send_from_directory()` will raise a `NotFound` when there is no file there. So `os.path.isfile(app.config['UPLOAD_FOLDER']/filename)` returned false. You need to doublecheck the contents of `UPLOAD_FOLDER` and verify that the filename you are trying to load is *really* there. – Martijn Pieters Jul 04 '14 at 15:30
  • It is there, changed app = Flask(__name__) to app = Flask(__name__, static_folder='uploads') and for some reasons it worked. – Saimu Jul 04 '14 at 15:40
  • Sounds like you are getting a not-found for the `/static/filename` route then instead of `/uploads/filename`. – Martijn Pieters Jul 04 '14 at 15:41
  • Yah, I don't really get it but it's working... I'll keep it this way :S – Saimu Jul 04 '14 at 15:42
  • I'm not sure that that is a good idea; you now are mixing static assets with uploads. – Martijn Pieters Jul 04 '14 at 15:43
  • It *sounds* as if `app.config['UPLOAD_FOLDER']` does not contain what you think it contains. What is it set to? – Martijn Pieters Jul 04 '14 at 15:44
  • to '/uploads/' , maybe its the slashes ? It was it. I changed '/uploads/' to 'uploads it's working ! Thanks to you ! – Saimu Jul 04 '14 at 16:29
  • 1
    Python deals with the trailing slash just fine. It is the **leading** slash that is the issue here. You have defined an absolute path, not a relative one. – Martijn Pieters Jul 04 '14 at 16:29

5 Answers5

103

You need to make sure that the value you pass to the directory argument is an absolute path, corrected for the current location of your application.

The best way to do this is to configure UPLOAD_FOLDER as a relative path (no leading slash), then make it absolute by prepending current_app.root_path:

@app.route('/uploads/<path:filename>', methods=['GET', 'POST'])
def download(filename):
    uploads = os.path.join(current_app.root_path, app.config['UPLOAD_FOLDER'])
    return send_from_directory(uploads, filename)

It is important to reiterate that UPLOAD_FOLDER must be relative for this to work, e.g. not start with a /.

A relative path could work but relies too much on the current working directory being set to the place where your Flask code lives. This may not always be the case.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • Flask 2.0 [replaced](https://stackoverflow.com/a/67591487/673991) the required `filename=` parameter with `path=`. I suspect the new optional filename parameter is the client-side name but the [doc](https://flask.palletsprojects.com/en/2.0.x/api/#flask.send_from_directory) doesn't say. Double eye roll emoji. – Bob Stein Jul 13 '23 at 18:55
  • 1
    @BobStein: the `filename` parameter is gone entirely now, but it only existed in the 2.0.x and 2.1.x series of releases to [trigger a warning](https://github.com/pallets/flask/blob/2.0.x/src/flask/helpers.py#L691-L698) (so, if you used it, you'd see a message that your should use `path=` now). You can use the names as positional arguments in any version. – Martijn Pieters Jul 15 '23 at 22:06
97

To download file on flask call. File name is Examples.pdf When I am hitting 127.0.0.1:5000/download it should get download.

Example:

from flask import Flask
from flask import send_file
app = Flask(__name__)

@app.route('/download')
def downloadFile ():
    #For windows you need to use drive name [ex: F:/Example.pdf]
    path = "/Examples.pdf"
    return send_file(path, as_attachment=True)

if __name__ == '__main__':
    app.run(port=5000,debug=True) 
Viraj Wadate
  • 5,447
  • 1
  • 31
  • 29
8

I was also developing a similar application. I was also getting not found error even though the file was there. This solve my problem. I mention my download folder in 'static_folder':

app = Flask(__name__,static_folder='pdf')

My code for the download is as follows:

@app.route('/pdf/<path:filename>', methods=['GET', 'POST'])
def download(filename):    
    return send_from_directory(directory='pdf', filename=filename)

This is how I am calling my file from html.

<a class="label label-primary" href=/pdf/{{  post.hashVal }}.pdf target="_blank"  style="margin-right: 5px;">Download pdf </a>
<a class="label label-primary" href=/pdf/{{  post.hashVal }}.png target="_blank"  style="margin-right: 5px;">Download png </a>
Waqar Detho
  • 1,502
  • 18
  • 17
  • 4
    You don't need or even *want* to set `static_folder` to point to your pdf subdirectory too. That just means that everything from the `/static/` path is *also* being served from the `pdf `subfolder. And as I stated in my answer: *A relative path **could** work but relies too much on the current working directory being set to the place where your Flask code lives.*; your relative path in `directory='pdf'` is going to fall in that trap too. – Martijn Pieters Oct 01 '16 at 13:19
8
#HTML Code
 
<ul>
 {% for file in files %}
  <li> <a href="{{ url_for('download', filename=file) }}">{{ file }}</a></li>
 {% endfor %}
</ul>


#Python Code

from flask import send_from_directory

app.config['UPLOAD_FOLDER']='logs'


@app.route('/uploads/<path:filename>', methods=['GET', 'POST'])
def download(filename):
    print(app.root_path)
    full_path = os.path.join(app.root_path, app.config['UPLOAD_FOLDER'])
    print(full_path)
    return send_from_directory(full_path, filename)

Gaurav Nagar
  • 191
  • 2
  • 4
0

Try this(assuming UPLOAD_FOLDER is relative to your working directory).

@app.route('/uploads/<path:filename>', methods=['GET', 'POST'])
def download(filename):
    path = os.path.join(os.getcwd(), app.config['UPLOAD_FOLDER'])
    return send_from_directory(directory=path, filename=filename)
Shirantha Madusanka
  • 1,461
  • 1
  • 11
  • 16