Python: try - except block
Tags: python
Published: 2024-06-28 09:29:31

Generally it is good practice to keep the `try` and `except` blocks as small and specific as possible. This is because it helps you identify exactly which section of your code might be causing an exception, making debugging easier and ensuring that you don't inadvertently catch exceptions that you didn't intend to handle.

Applying this principle to the `save` method from article: In [Django model: save an image with Pillow (PIL) library ](https://dev.to/doridoro/in-django-model-save-an-image-with-pillow-pil-library-6be-temp-slug-7394826?preview=9e9d3d772257846bba4c19b1591be9fbee60ad1357db4e5bf6c8ab17957357aa532166814a9a07c3eb0c6e1ffd1421c675984e09f963c673ca285be6), you'll want to isolate the parts of the code that could potentially raise an exception and use `try`-`except` blocks specifically around those sections.

Here's a refactored `save` method with more focused `try`-`except` blocks:

```python
from django.core.files.base import ContentFile
from io import BytesIO
from PIL import Image, ImageOps

class Picture(models.Model):
    legend = models.CharField(max_length=100)
    photo = models.ImageField(
        upload_to="images/",
        blank=True,
        null=True,
    )
    published = models.BooleanField(default=True)

    def __str__(self):
        return self.legend

    def save(self, *args, **kwargs):
        if self.photo:
            try:
                img = Image.open(self.photo)
                img.verify()
            except (IOError, SyntaxError) as e:
                raise ValueError(f"The uploaded file is not a valid image. -- {e}")

            # Reopen the image to reset the file pointer
            try:
                img = Image.open(self.photo)
            except (IOError, SyntaxError) as e:
                raise ValueError(f"The uploaded file could not be reopened as an image. -- {e}")
            
            if img.mode in ("RGBA", "LA", "P"):
                img = img.convert("RGB")

            # Calculate new dimensions to maintain aspect ratio with a width of 800
            new_width = 800
            original_width, original_height = img.size
            new_height = int((new_width / original_width) * original_height)

            try:
                # Resize the image
                img = img.resize((new_width, new_height), Image.LANCZOS)

                # Save the image as JPEG
                temp_img = BytesIO()
                img.save(temp_img, format="JPEG", quality=70, optimize=True)
                temp_img.seek(0)

                # Change file extension to .jpg
                original_name, _ = self.photo.name.lower().rsplit(".", 1)
                img_filename = f"{original_name}.jpg"

                # Save the BytesIO object to the ImageField with the new filename
                self.photo.save(img_filename, ContentFile(temp_img.read()), save=False)
            except (IOError, SyntaxError) as e:
                raise ValueError(f"An error occurred while processing the image. -- {e}")
                
        super().save(*args, **kwargs)
```

### Breakdown of Changes:
1. **Initial Image Verification:**
   - Isolated the `Image.open(self.photo)` and `img.verify()` calls into their own `try` block to catch errors specific to loading and verifying the image.

2. **Image Reopen:**
   - Isolated the `Image.open(self.photo)` again after `verify()` to handle potential errors specific to reopening the image.

3. **Image Processing (Resize, Convert to JPEG):**
   - Isolated the resizing and conversion logic into its own `try` block to handle exceptions that could occur during image processing like resizing and saving as JPEG.

By doing this, if an error occurs, it’s clearer which section of code caused the exception. This makes your error handling both more precise and more robust.