3

I'm working with Django 1.7 and Python 3.4.

I have a model like this one:

class ImageModel(models.Model):
    image = models.ImageField(verbose_name='image', upload_to='uploaded_images/')

Now I want to download image which is saved in /static/uploaded_images/. For example I have a link like this: www.example.com/image/download/1, where 1 is id of ImageModel object.

Now i have a view:

def download_image(request, image_id):
     img = ImageModel.objects.get(id=image_id)
     ( what I need to do? )

What next? How to create a view that will force download of that image?

Szymon Barylak
  • 92
  • 1
  • 2
  • 8

7 Answers7

4

You can try this code, maybe need some caveats:

from django.core.servers.basehttp import FileWrapper
import mimetypes

def download_image(request, image_id):
    img = ImageModel.objects.get(id=image_id)
    wrapper      = FileWrapper(open(img.file))  # img.file returns full path to the image
    content_type = mimetypes.guess_type(filename)[0]  # Use mimetypes to get file type
    response     = HttpResponse(wrapper,content_type=content_type)  
    response['Content-Length']      = os.path.getsize(img.file)    
    response['Content-Disposition'] = "attachment; filename=%s" %  img.name
    return response
  1. I'm assuming there is a field .name in your ImageModel to get the name of the file in the second-to-last line ...filename=%s" % img.name You should edit the code to fit your project.

  2. There is a field in an ImageField that is file, in the code here I use img.file to get the path to the file, you should change that for img.YOUR_IMAGE_FIELD.file or anything you need to get the path to the image

AlvaroAV
  • 10,335
  • 12
  • 60
  • 91
  • Please note that this will work fine when the names are within the ASCII range, but fails miserably with wide characters. Content-Disposition has support for a UTF-8 encoded name, but older browsers (pre IE8) will ignore this completely. Internally it works better to have a hidden redirect to a URL with the filename in the URL. All browsers will support this and you'll avoid the nasty `[1]` error that IE will attach to whacked out filenames – Mike McMahon Oct 26 '14 at 20:49
  • @MikeMcMahon I'm not completely sure about what are you trying to explain, but I'd be glad to learn any other way to do this if it's better. Could you give some link to info about this ? – AlvaroAV Oct 26 '14 at 20:51
  • Consider filenames with Kanji characters, or cyrillic characters. `filename=%s` will not work. It will work in FireFox / Chrome but not <= IE8 and possibly mobile browsers (depending on which mobile device and browser technology used). There is a `filename*=UTF-8''%s` format which supports UTF-8 encoded characters, and this will work *sometimes*. It is best to (from within DJango) perform a HttpRedirect() to a URL containing the full filename as part of the path. ALL browsers can interpret this and characters outside the standard range. – Mike McMahon Oct 26 '14 at 20:54
  • Ahhh, now I understand, I allways use unicode characters in my projects and never had this issue, thanks you for the info! I'm also interested in the different way you use to send the file to the users – AlvaroAV Oct 26 '14 at 20:56
1

You need to use Content-Disposition header, take a look here:

Generating file to download with Django
Django Serving a Download File

Community
  • 1
  • 1
Hubert Barc
  • 59
  • 1
  • 6
1

A class based views-type exampe of this would be like this (i'm using python-magic to get the correct content-type for the file):

import os
import magic

from django.views.generic import View
from django.http import HttpResponse

from .models import ImageModel


class ImageDownloadView(View):

    def get(self, request, *args, **kwargs):
        image = ImageModel.objects.get(pk=self.kwargs['image_id'])
        image_buffer = open(image.file.path, "rb").read()
        content_type = magic.from_buffer(image_buffer, mime=True)
        response = HttpResponse(image_buffer, content_type=content_type);
        response['Content-Disposition'] = 'attachment; filename="%s"' % os.path.basename(image.file.path)
        return response

This works for Django 1.10.7 but shouldn't be that different for Django 1.7

Ramon de Jesus
  • 779
  • 6
  • 10
0

The other two answers are ok, but as advertised in many places using Django to serve an static file is not recommended for performance reasons. It is better to serve it using your web server (nginx/apache...).

You don't need an extra view to serve static files. Simply render a link to the file in your template:

<a href="{{object.image.url}} download">Download this image!</a>

Where object is an instance of ImageModel.

See django.db.models.fields.files.FieldFile.url

If you really want to have a view in an URL like www.example.com/image/download/1 you can simply write a view that redirect to the image URL obtained from the field.

dukebody
  • 7,025
  • 3
  • 36
  • 61
0

This code works fine in new versions of Django:

import mimetypes

def download_image(request, id):
    img = ImageModel.objects.get(id=id)
    wrapper = img.field_name
    content_type = mimetypes.guess_type(img.field_name.name)[0]
    response = HttpResponse(wrapper, content_type=content_type)
    response['Content-Length'] = img.field_name.size
    response['Content-Disposition'] = f"attachment; filename={img.field_name.name}"
    return response

Change field_name to your field. Field has to be a ImageField or FileField.

ivall
  • 27
  • 7
-1

Here is your cross browser safe download for files containing any character type

# Even better as it works in any browser (mobile and desktop)
def safe_name(file_name):
    """
    Generates a safe file name, even those containing characters like ? and &
    And your Kanji and Cyrillics are supported! 
    """
    u_file_name = file_name.encode('utf-8')
    s_file_name = re.sub('[\x00-\xFF]', lambda c: '%%%02x' % ord(c.group(0)), u_file_name)
    return s_file_name

# Handled by url(r'^/image/download/(\d+)/.+$
def safe_download_image(request, image_id):
    """ 
    Safely downloads the file because the filename is part of the URL
    """
    img = ImageModel.objects.get(id=image_id)
    wrapper      = FileWrapper(open(img.file))  # img.file returns full path to the image
    content_type = mimetypes.guess_type(filename)[0]  # Use mimetypes to get file type
    response     = HttpResponse(wrapper,content_type=content_type)  
    response['Content-Length']      = os.path.getsize(img.file)     
    # This works for most browsers, but IE will complain sometimes
    response['Content-Disposition'] = "attachment;"
    return response

def download_image(request, image_id):
    img = ImageModel.objects.get(id=image_id)
    redirect_do = safe_name(img.name)
    return HttpResponseRedirect('/image/download/' + img_id + '/' + redirect_to)
Mike McMahon
  • 7,096
  • 3
  • 30
  • 42
-1

It's doesn't work. I did something like this:

wrapper = FileWrapper(img.file)  # img.file returns full path to the image
content_type = mimetypes.guess_type(str(img.file))[0]  # Use mimetypes to get file type
response = HttpResponse(wrapper, content_type=content_type)
response['Content-Length'] = os.path.getsize(str(img.file))
response['Content-Disposition'] = "attachment; filename=%s" % img.name

where img points to my ImageField field. File is downloaded, but I can't open it. xUbuntu image viewer seys 'Not a JPEG file. starts with 0x89 0x50'

Szymon Barylak
  • 92
  • 1
  • 2
  • 8
  • the first two bytes of ANY JPEG file should be `0xFF` and `0xE0`respectively. Are you at any point not treating the image as binary while uploading or downloading? any operations that affect the content should be opening the file in 'rb' or 'wb' mode respectively. – Mike McMahon Oct 27 '14 at 21:36