2
\$\begingroup\$

I have implemented following code to upload and resize image in Django. Please suggest ways to make it more pythonic.

def view(request, spot_id)
 IMAGE_DIR = MEDIA_ROOT + 'spot/' + spot_id 
 if request.method == "POST":
 form = PublishedSpotForm(request.POST, request.FILES)
 if form.is_valid():
 cleaned_data = form.cleaned_data.copy()
 # dimension for logo 160x160 , 80x80, 64x64, 40x40
 # dimension for cover 800x600 , 600x480, 480x360, 200x150
 if 'logo' in request.FILES:
 logo_path = handle_uploaded_image(IMAGE_DIR, 'logo', request.FILES['logo'], width=160,
 height=160)
 logo_path = handle_uploaded_image(IMAGE_DIR, 'logo', request.FILES['logo'], width=80, height=80)
 logo_path = handle_uploaded_image(IMAGE_DIR, 'logo', request.FILES['logo'], width=64, height=64)
 logo_path = handle_uploaded_image(IMAGE_DIR, 'logo', request.FILES['logo'], width=40, height=40)
 logo_path = handle_uploaded_image(IMAGE_DIR, 'logo', request.FILES['logo'], width=None, height=None)
 if "logo" in cleaned_data:
 cleaned_data.pop("logo")
 cleaned_data["logo"] = logo_path
 if 'cover' in request.FILES:
 cover_path = handle_uploaded_image(IMAGE_DIR, 'cover', request.FILES['cover'], width=None, height=None)
 cover_path = handle_uploaded_image(IMAGE_DIR, 'cover', request.FILES['cover'], width=800,
 height=600)
 cover_path = handle_uploaded_image(IMAGE_DIR, 'cover', request.FILES['cover'], width=600,
 height=480)
 cover_path = handle_uploaded_image(IMAGE_DIR, 'cover', request.FILES['cover'], width=480,
 height=360)
 cover_path = handle_uploaded_image(IMAGE_DIR, 'cover', request.FILES['cover'], width=200,
 height=150)
 if 'cover' in cleaned_data:
 cleaned_data.pop('cover')
 cleaned_data['cover'] = cover_path
 ...

The method for re-sizing an image:

def handle_uploaded_image(save_to, image_name, image_file, width=None, height=None):
 import os, hashlib
 import StringIO
 from django.core.files import File
 from PIL import Image, ImageOps
 # todo throw exception if path isn't available
 if not os.path.exists(save_to):
 os.makedirs(save_to)
 # read image from InMemoryUploadedFile
 str = ""
 for c in image_file.chunks():
 str += c
 # create PIL Image instance
 imagefile = StringIO.StringIO(str)
 image = Image.open(imagefile)
 if width is None or height is None:
 filename = image_name + '.jpg'
 imagefile = open(os.path.join(save_to, filename), 'w')
 image.save(imagefile, 'JPEG', quality=90)
 return True
 # if not RGB, convert
 if image.mode not in ("L", "RGB"):
 image = image.convert("RGB")
 # define file output dimensions (ex 60x60)
 # get orginal image ratio
 img_ratio = float(image.size[0]) / image.size[1]
 # resize but constrain proportions?
 if width == 0.0:
 width = height * img_ratio
 elif height == 0.0:
 height = width / img_ratio
 # output file ratio
 resize_ratio = float(width) / height
 width = int(width)
 height = int(height)
 # get output with and height to do the first crop
 if (img_ratio > resize_ratio):
 output_width = width * image.size[1] / height
 output_height = image.size[1]
 originX = image.size[0] / 2 - output_width / 2
 originY = 0
 else:
 output_width = image.size[0]
 output_height = height * image.size[0] / width
 originX = 0
 originY = image.size[1] / 2 - output_height / 2
 # crop
 cropBox = (originX, originY, originX + output_width, originY + output_height)
 image = image.crop(cropBox)
 # resize (doing a thumb)
 image.thumbnail([width, height], Image.ANTIALIAS)
 # re-initialize imageFile and set a hash (unique filename)
 filename = image_name + '_%dx%d.jpg' % (width, height)
 # save to disk
 imagefile = open(os.path.join(save_to, filename), 'w')
 image.save(imagefile, 'JPEG', quality=90)
 imagefile = open(os.path.join(save_to, filename), 'r')
 content = File(imagefile)
 return save_to
Jamal
35.2k13 gold badges134 silver badges238 bronze badges
asked Aug 7, 2014 at 5:01
\$\endgroup\$

2 Answers 2

3
\$\begingroup\$

Some comments:

  • Note that to overwrite a value in a dictionary you just need to set it, there's no need to pop the value out of the dictionary
  • Follow the DRY principle
    • Instead of a line for set of dimensions use a loop
    • Instead of repeating for each file, use a function

With the suggested changes, the view code would be more or less as follows:

def view(request, spot_id):
 IMAGE_DIR = MEDIA_ROOT + 'spot/' + spot_id
 LOGO_DIMENSIONS = [(160, 160), (80, 80), (64, 64), (40, 40), (None, None)]
 COVER_DIMENSIONS = [(None, None), (800, 600), (600, 480), (480, 360), (200, 150)]
 def handle_uploaded_image_with_dimensions(name, dimensions):
 """Upload image with different dimensions."""
 if name in request.FILES:
 for width, height in dimensions:
 path = handle_uploaded_image(
 IMAGE_DIR,
 name,
 request.FILES[name],
 width=width,
 height=height)
 cleaned_data[name] = path
 if request.method == "POST":
 form = PublishedSpotForm(request.POST, request.FILES)
 if form.is_valid():
 cleaned_data = form.cleaned_data.copy()
 # dimension for logo 160x160 , 80x80, 64x64, 40x40
 # dimension for cover 800x600 , 600x480, 480x360, 200x150
 handle_uploaded_image_with_dimensions('logo', LOGO_DIMENSIONS)
 handle_uploaded_image_with_dimensions('cover', COVER_DIMENSIONS)
answered Aug 7, 2014 at 7:41
\$\endgroup\$
2
\$\begingroup\$

Here:

IMAGE_DIR = MEDIA_ROOT + 'spot/' + spot_id 

Use os.path.join to compose the path

Here:

 cleaned_data = form.cleaned_data.copy()

I don't think you really need to make a copy of cleaned_data... what's bad in modifying it?

Here:

 logo_path = handle_uploaded_image(IMAGE_DIR, 'logo', request.FILES['logo'], width=160,
 height=160)
 logo_path = handle_uploaded_image(IMAGE_DIR, 'logo', request.FILES['logo'], width=80, height=80)
 logo_path = handle_uploaded_image(IMAGE_DIR, 'logo', request.FILES['logo'], width=64, height=64)
 logo_path = handle_uploaded_image(IMAGE_DIR, 'logo', request.FILES['logo'], width=40, height=40)
 logo_path = handle_uploaded_image(IMAGE_DIR, 'logo', request.FILES['logo'], width=None, height=None)

use iteration. Store your list of sizes and iterate over it. I would only leave apart the last case (None,None) because it is the only one of which you really use the return value (so, don't store the return value when you don't use it).

Here:

 if "logo" in cleaned_data:
 cleaned_data.pop("logo")
 cleaned_data["logo"] = logo_path

the if part can be skipped since the item will be anyway popped in the following line.

Here:

str = ""
for c in image_file.chunks():
 str += c

use bytes instead of string. The code will look more explicit and would be ready for python3.

answered Aug 7, 2014 at 7:38
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.