This site runs best with JavaScript enabled.

Django Custom Upload Handler to Compress Image Files


How to compress images when they are saved in a Django application.

I got a somewhat unique request on a project the other day. My client has a lead tracking system where his salesman input leads and often upload scanned documents to include with the leads. I implemented this all with standard Django forms and a formset wizard to input multiple files.

My client was worried that a lot of images would be uploaded and he would have to start paying extra for storage. He asked if I could compress images on upload to save space. After searching the web I found examples of a few different ways of doing it. But after reading about Upload Handlers in the Django docs, this seemed like it would be the best method for accomplishing this so I wouldn't have to modify my models or forms at all. Unfortunately for me, it didn't go as straightforward as I had hoped. I couldn't find a good example of someone else doing this sort of thing and it took me MUCH longer than the 30-45 minutes I had planned for.

The good news is that I figured it out so I'm posting it here for all to benefit hopefully.

I created a file named uploadhandlers.py in my app and added the following code:

1import os
2
3 from django.conf import settings
4 from django.core.files.uploadhandler import MemoryFileUploadHandler
5 from PIL import Image
6
7 try:
8 from cStringIO import StringIO
9 except ImportError:
10 from StringIO import StringIO
11
12 class CompressImageUploadHandler(MemoryFileUploadHandler):
13 def file_complete(self, file_size):
14 """
15 Return a file object if we're activated.
16 """
17 self.file.seek(0)
18 if not self.content_type is None and 'image' in self.content_type:
19 newfile = StringIO()
20 img = Image.open(self.file)
21 width, height = img.size
22 width, height = scale_dimensions(width, height, longest_side=settings.IMAGE_LONGEST_SIDE)
23 img = img.resize((width, height), Image.ANTIALIAS)
24 img.save(newfile, 'JPEG', quality=settings.JPEG_QUALITY)
25 self.file = newfile
26
27 name, ext = os.path.splitext(self.file_name)
28 self.file_name = '{0}.{1}'.format(name, 'jpg')
29 self.content_type = 'image/jpeg'
30
31 return super(CompressImageUploadHandler, self).file_complete(file_size)
32
33 def scale_dimensions(width, height, longest_side):
34 if width 1:
35 return longest_side, int(longest_side / ratio)
36 # Portrait
37 else:
38 return int(longest_side * ratio), longest_side

You can see from the code that I am simply extending the MemoryFileUploadHandler, which is one of the Django default upload handlers. I'm overriding the file_complete function to change the size and jpeg quality - which are settings in my settings file.

To implement the change, I update my views. The view that contains the form has to be csrf_exempt, and the view handling the uploads switches to this upload handler on the fly with the following code:

1request.upload_handlers.insert(0, CompressImageUploadHandler())

Discuss on TwitterEdit post on GitHub

Share article
Dustin Davis

Dustin Davis is a software engineer, people manager, hacker, and entreprenuer. He loves to develop systems and automation. He lives with his wife and five kids in Utah.

Join the Newsletter



Dustin Davis