Image upload and crop in the Django Rest framework

Last updated: April 18, 2024

Image upload and crop in the Django Rest framework

After image uploading, cropping is a very necessary post-processing step.

Necessity of image cropping:

  1. To get the exact size of the of the image (e.g : 300 * 300)
  2. To create a thumbnail
  3. To create multiple versions of the image for the e-commerce site (left crop, right crop image)

 

Install required packages:

Make sure you have Django and Django Rest Framework installed also install Pillow.

pip install pillow

 

Model for Image Upload:

Define a model in your models.py file to handle image uploads. Use ImageField for storing images:

# yourappname/models.py

from django.db import models

class CroppedImage(models.Model):
    title = models.CharField(max_length=255)
    original_image = models.ImageField(upload_to='original_images/')
    cropped_image = models.ImageField(upload_to='cropped_images/', null=True, blank=True)

original_image filed store uploading raw image. It is a backup image. After cropping, the modified image will be saved in the cropped_image field.

 

Don't forget to run migrations:

python manage.py makemigrations
python manage.py migrate

 

Serializer:

The serializer is the best place for cropping operations. In serializer, I created a function called center_crop_image that takes the original image file and, using the pillow library, center-crops it and returns it as bytes. For demonstration purposes, I cropped the image to 300 * 300 size. You can change it according to your requirements. At last, save the returned bytes in the cropped_image field. 

If the original image width or height is less than the cropped width or height, it will be an exception. You should handle it according to your needs.

# yourappname/serializers.py

from rest_framework import serializers
from myapp.models import CroppedImage
from PIL import Image
from io import BytesIO
from django.core.files.base import ContentFile

class CroppedImageSerializer(serializers.ModelSerializer):
    class Meta:
        model = CroppedImage
        fields = ['pk', 'title', 'original_image', 'cropped_image']

    def create(self, validated_data):
        original_image = validated_data.get('original_image')
        instance = super().create(validated_data)

        # Center crop the image
        cropped_image = self.center_crop_image(original_image)
        instance.cropped_image.save(original_image.name, ContentFile(cropped_image), save=False)
        instance.save()

        return instance

    def center_crop_image(self, image):
        img = Image.open(image)
        width, height = img.size
        min_dim = min(width, height)
        left = (width - min_dim) / 2
        top = (height - min_dim) / 2
        right = (width + min_dim) / 2
        bottom = (height + min_dim) / 2
        cropped_img = img.crop((left, top, right, bottom))

        # Resize the cropped image to a desired size (e.g., 300x300)
        cropped_img = cropped_img.resize((300, 300), resample=Image.BICUBIC)

        # Convert the cropped image to bytes
        buffer = BytesIO()
        cropped_img.save(buffer, format='JPEG')
        img_bytes = buffer.getvalue()

        return img_bytes

center_crop_image function Take a parameter named image; that type is InMemoryUploadedFile. It is a subclass of Python File. So the Image.open function can take the parameter and open it in write mode.

 

Views:

Create a view in your views.py file to handle image uploads:

# yourappname/views.py

from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework import status
from myapp.models import CroppedImage
from .serializers import CroppedImageSerializer

@api_view(['POST'])
def cropped_image_api_view(request):
    if request.method == 'POST':
        serializer = CroppedImageSerializer(data=request.data)

        if serializer.is_valid():
            serializer.save()
            return Response(serializer.data, status=status.HTTP_201_CREATED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)

 

URL Configuration:

Configure your URLs in urls.py to include the DRF view:

# yourappname/urls.py

from myapp.api.views import (

    cropped_image_api_view
)



urlpatterns = [

    path("api/cropped-image", cropped_image_api_view, name="cropped_image")
]