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
\$\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)
\$\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.

\$\endgroup\$

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.