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
2 Answers 2
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)
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.