[Fixed]-Removing tmp file after return HttpResponse in django


You can use a NamedTemporaryFile:

from django.core.files.temp import NamedTemporaryFile
def send_file(request):
    newfile = NamedTemporaryFile(suffix='.txt')
    # save your data to newfile.name
    wrapper = FileWrapper(newfile)
    response = HttpResponse(wrapper, content_type=mime_type)
    response['Content-Disposition'] = 'attachment; filename=%s' % os.path.basename(modelfile.name)
    response['Content-Length'] = os.path.getsize(modelfile.name)
    return response

temporary file should be deleted once the newfile object is evicted.



For future references:
I just had the case in which I couldn’t use temp files for downloads.
But I still needed to delete them after it; so here is how I did it (I really didn’t want to rely on cron jobs or celery or wossnames, its a very small system and I wanted it to stay that way).

def plug_cleaning_into_stream(stream, filename):
        closer = getattr(stream, 'close')
        #define a new function that still uses the old one
        def new_closer():
            #any cleaning you need added as well
        #substitute it to the old close() function
        setattr(stream, 'close', new_closer)

and then I just took the stream used for the response and plugged into it.

def send_file(request, filename):
    with io.open(filename, 'rb') as ready_file:
        plug_cleaning_into_stream(ready_file, filename)
        response = HttpResponse(ready_file.read(), content_type='application/force-download')
        # here all the rest of the heards settings
        # ...
        return response

I know this is quick and dirty but it works. I doubt it would be productive for a server with thousands of requests a second, but that’s not my case here (max a few dozens a minute).

EDIT: Forgot to precise that I was dealing with very very big files that could not fit in memory during the download. So that is why I am using a BufferedReader (which is what is underneath io.open())


Mostly, we use periodic cron jobs for this.

Django already has one cron job to clean up lost sessions. And you’re already running it, right?

See http://docs.djangoproject.com/en/dev/topics/http/sessions/#clearing-the-session-table

You want another command just like this one, in your application, that cleans up old files.

See this http://docs.djangoproject.com/en/dev/howto/custom-management-commands/

Also, you may not really be sending this file from Django. Sometimes you can get better performance by creating the file in a directory used by Apache and redirecting to a URL so the file can be served by Apache for you. Sometimes this is faster. It doesn’t handle the cleanup any better, however.



One way would be to add a view to delete this file and call it from the client side using an asynchronous call (XMLHttpRequest). A variant of this would involve reporting back from the client on success so that the server can mark this file for deletion and have a periodic job clean it up.


This is just using the regular python approach (very simple example):

# something generates a file at filepath

from subprocess import Popen

# open file
with open(filepath, "rb") as fid:
    filedata = fid.read()

# remove the file
p = Popen("rm %s" % filepath, shell=True)

# make response
response = HttpResponse(filedata, content-type="text/plain")

return response


Python 3.7 , Django 2.2.5

from tempfile import NamedTemporaryFile
from django.http import HttpResponse
with NamedTemporaryFile(suffix='.csv', mode='r+', encoding='utf8') as f:
    f.write('\uFEFF')  # BOM
    f.write('sth you want')

    # ref: https://docs.python.org/3/library/tempfile.html#examples

    response = HttpResponse(data, content_type="text/plain")
    response['Content-Disposition'] = 'inline; filename=export.csv'


You can simply achieve that by try / finally:

    HttpResponse(...) / FileResponse(...)
    os.remove(tmp_file) / shutil.rmtree(path_to_tmp_file)

Leave a comment