Django: How to let user download a file

Creating file download links with the help of a FileResponse.

Published: July 23, 2022
App Store

In this post, we will look at how to download a file with Django. Specifically how to let users download ImageField or FileField content.

While there are a few ways to accomplish this, using FileResponse works really well. The most basic version is as simple as returning the FileResponse from your view like this:

return FileResponse(model.image.open())

Here the image property is ImageField on a model.

This won’t download the file; rather the browser will open it in a new tab in most cases. A similar thing will also happen for other file types that browsers can preview.

To download it, we should specify the as_attachment parameter:

return FileResponse(model.image.open(), as_attachment=True)

The downloaded file will keep whatever filename used during upload (assuming you aren’t generating those yourself during save), which may not be optimal.

Custom filename

Wanting to “override” the filename was the main reason for this blog post. It turns out, you can pass any filename you wish to FileResponse, and that is the one browser will use when saving the file.

Passing the filename parameter will allow you to offer descriptive filenames and possibly avoid embarrassing moments.

return FileResponse(model.image.open(), as_attachment=True, filename="Export.png")

That works great, but what if only some images are PNGs and others are JPEGs?

In my testing, when the filename did not contain the extension, I could still download the files without issues, and macOS added the extension during saving. However, on Windows the files did not have an extension and looked like something went wrong. To be honest, I did not test this thoroughly, so perhaps it was due to different browsers.

The point is that we should specify file extension ourselves to avoid this problem.

We can use pathlib to get the extension and add it to the generated filename:

extension = pathlib.Path(model.image.name).suffix
filename_with_extension = "{0}{1}".format(filename, extension)

return FileResponse(model.image.open(), as_attachment=True, filename=filename_with_extension)

Downloading generated image

If your web app provides images that it generated, and aren’t saved as file, you can still make them available to download without saving them first. But FileResponse won’t help there.

response = HttpResponse(content_type='image/png')
image.save(response, 'png')
response['Content-Disposition'] = 'attachment; filename={0}'.format("Export.png")

return response

The header Content-Disposition controls whether the file is downloaded (not previewed) and its filename. This is what FileResponse does for us behind the scenes. The above example assumes that image is of type PIL.Image.

Download FileField content

As mentioned previously, FileField downloads work mostly the same. The difference is that we don't have to open the file.

return FileResponse(model.file, as_attachment=True)
Bluesky logo

Follow on Bluesky to not miss new posts

Filip Němeček profile photo

WRITTEN BY

Filip Němeček Mastodon

iOS blogger and developer with interest in Python/Django.

iOS blogger and developer with interest in Python/Django.