2023 Update! I just rewrite this article in my website. See how to do with only Python 3.11
and also with Django 4.2
.
Access the article here!
A few days ago, while developing the bazaar article register, I fell into a somewhat complicated situation. Perhaps yours is similar.
When creating a new article in the bazaar, the selected images are read using the FileReader API
to generate the dataURL of the images in order to save it allowing the user to leave the bazaar, go back and continue creating the article with the selected images.
As the reference of the file
of each image is lost, when uploading the images it was necessary to send the dataURL instead of the file itself, in this case the API should be in charge of generating the image files
Note: Using Python 3.6.9 and Django in version 3.0.3
For this, the following libraries were necessary to carry out this small task.
# python standard lib
import base64, secrets, io
# django and pillow lib
from PIL import Image
from django.core.files.base import ContentFile
I defined this function capable of receiving the dataURL to generate the file and resize it if necessary.
def get_image_from_data_url( data_url, resize=True, base_width=600 ):
# getting the file format and the necessary dataURl for the file
_format, _dataurl = data_url.split(';base64,')
# file name and extension
_filename, _extension = secrets.token_hex(20), _format.split('/')[-1]
# generating the contents of the file
file = ContentFile( base64.b64decode(_dataurl), name=f"{_filename}.{_extension}")
# resizing the image, reducing quality and size
if resize:
# opening the file with the pillow
image = Image.open(file)
# using BytesIO to rewrite the new content without using the filesystem
image_io = io.BytesIO()
# resize
w_percent = (base_width/float(image.size[0]))
h_size = int((float(image.size[1])*float(w_percent)))
image = image.resize((base_width,h_size), Image.ANTIALIAS)
# save resized image
image.save(image_io, format=_extension)
# generating the content of the new image
file = ContentFile( image_io.getvalue(), name=f"{_filename}.{_extension}" )
# file and filename
return file, ( _filename, _extension )
Well, at this point, we don't actually have the generated file yet, just the contents of the file (Using the Django ContentFile API that returns an instance of it) stored in memory and ready to be written to a file in the filesystem
However, with the file
that is returned, we only need to do the following to generate the file and save usingdefualt filesystem storage
:
Example:
# for example
from .models import User
from .utils.images import get_image_from_data_url
def create_user_view(request)
username = request.POST.get('username')
# getting the file instance
avatar_file = get_image_from_data_url(request.POST.get('avatar'))[0]
# create a user and generate and save the file using the default filesystem storage
user = User.objects.create(
username=username,
avatar=avatar_file
)
# this does not work 😂
return Response(user, status=status.HTTP_201_CREATED)
Of course you will do more validations, after all in your case it will not be a mere example right 😉
As a bonus 😎, I defined a second function capable of generating the content of the file for the main image and for a second thumbnail image (image copy but with a much smaller size, suitable for lazy load images
among other situations).
def get_image_and_thumbnail_from_data_url( data_url, resize=True, base_width=600):
#
file, filename = get_image_from_data_url(data_url, resize, base_width)
#
thumbnail = Image.open(file)
#
thumbnail_io = io.BytesIO()
thumbnail.thumbnail((128,128), Image.ANTIALIAS)
thumbnail.save(thumbnail_io, format=filename[1])
# thumbnail image
thumbnail = ContentFile(
thumbnail_io.getvalue(),
name=f"{filename[0]}.thumbnail.{filename[1]}"
)
return file, thumbnail
So, that's it for now! I hope this little tip can help you. But be careful that depending on the situation, these operations can be very costly. In case of multiple images, it is advisable to perform operations in the background, using Celery for example (maybe I will do a second article implementing Celery, what do you think? 🤔).
So that the user does not wait long before receiving a response from the server.
Be sure to comment on your debt, criticism or suggestion! Enjoy and follow my work on social networks.
Top comments (2)
This came in really handy...thanks
Amazing to know that I could help you 🥳