Django Custom Upload Handler to Compress Image Files
Photo by Patrick Tomasso on Unsplash
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 os23 from django.conf import settings4 from django.core.files.uploadhandler import MemoryFileUploadHandler5 from PIL import Image67 try:8 from cStringIO import StringIO9 except ImportError:10 from StringIO import StringIO1112 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.size22 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 = newfile2627 name, ext = os.path.splitext(self.file_name)28 self.file_name = '{0}.{1}'.format(name, 'jpg')29 self.content_type = 'image/jpeg'3031 return super(CompressImageUploadHandler, self).file_complete(file_size)3233 def scale_dimensions(width, height, longest_side):34 if width 1:35 return longest_side, int(longest_side / ratio)36 # Portrait37 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())