Starting with Django 2.1 (see issue #16470), you can use FileResponse, which will correctly set the Content-Disposition header for attachments. Starting with Django 3.0 (issue #30196) it will also set it correctly for inline files.
For example, to return a file named my_img.jpg with MIME type image/jpeg as an HTTP response:
response = FileResponse(open("my_img.jpg", 'rb'), as_attachment=True, content_type="image/jpeg")
return response
Starting with Django 4.2 you also have the option of setting the content disposition header with a utility function, for example
response.headers["Content-Disposition"] = content_disposition_header(...)
Django < 2.1
If you can't use FileResponse, you can use the relevant part from FileResponse's source to set the Content-Disposition header yourself. Here's what that source currently looks like:
from urllib.parse import quote
disposition = 'attachment' if as_attachment else 'inline'
try:
filename.encode('ascii')
file_expr = 'filename="{}"'.format(filename)
except UnicodeEncodeError:
file_expr = "filename*=utf-8''{}".format(quote(filename))
response.headers['Content-Disposition'] = '{}; {}'.format(disposition, file_expr)