# Create thumbnails from images and add products
First of all we need to add Pillow package to our dependencies:
$ poetry add Pillow
• Installing pillow (8.2.0)
Then we are going to restructure our handle_file_upload
function in the app/helpers.py
:
async def handle_file_upload(file: UploadFile) -> Tuple[str, str]:
content, ext, img_dir = await file_operations(file)
file_name = f'{uuid.uuid4().hex}{ext}'
async with aiofiles.open(os.path.join(img_dir, file_name), mode='wb') as f:
await f.write(content)
new_file = os.path.join(BASEDIR, f'statics/media/{file_name}')
thumbnail_name = f'thumb_{file_name}'
thumbnail_content = make_thumbnail(new_file)
async with aiofiles.open(os.path.join(img_dir, thumbnail_name), mode='wb') as f:
await f.write(thumbnail_content.read())
thumbnail_content.close()
return file_name, thumbnail_name
As you may notice, we have extracted some functionality to the file_operations
function:
async def file_operations(file: UploadFile) -> Tuple[bytes, str, str]:
_, ext = os.path.splitext(file.filename)
img_dir = os.path.join(BASEDIR, 'statics/media/')
if not os.path.exists(img_dir):
os.makedirs(img_dir)
content = await file.read()
if file.content_type not in ['image/jpeg', 'image/png']:
raise HTTPException(status_code=406, detail="Only .jpeg or .png files allowed")
return content, ext, img_dir
Now let's add make_thumbnail
function:
def make_thumbnail(file: str, size: tuple = (300, 200)) -> BytesIO:
img = Image.open(file)
rgb_im = img.convert('RGB')
rgb_im.thumbnail(size)
thumb_io = BytesIO()
rgb_im.save(thumb_io, format='PNG', quality=85)
thumb_io.seek(0)
return thumb_io
Basically we are sending to the make_thumbnail
the new uploaded file name and it will create new resized file in the same directory but with thumb_
prefix.
Our updated endpoint will be:
@router.post(
"/product/create",
tags=["create product"],
description="Create new product",
response_model=ProductInDB,
dependencies=[Depends(check_if_user_is_admin)]
)
async def product_create(category: int = Form(...),
name: str = Form(...),
slug: str = Form(...),
price: float = Form(...),
description: str = Form(...),
image: UploadFile = File(...)
) -> ProductInDB:
product = ProductCreate(category=category,
name=name,
slug=slug,
price=price,
description=description)
image_, thumb_image = await handle_file_upload(image)
product.image = image_
product.thumbnail = thumb_image
# here we put id=10 manually for test purposes originally it should came from database
return ProductInDB(id=10, **product.dict())
Basically we have returned back the name of the thumbnail and updated the Pydantic schemas field.
Sending our POST request to the endpoint:
Files are created accordingly:
❯ tree backend/app/statics/ -I __pycache__
backend/app/statics/
└── media
├── 0264f7d1d13343dd9ecbf7a9102b5f90.png
└── thumb_0264f7d1d13343dd9ecbf7a9102b5f90.png
Now it is time to save those new data in the database. We need to update our controller:
@router.post(
"/product/create",
tags=["create product"],
description="Create new product",
response_model=ProductInDB,
dependencies=[Depends(check_if_user_is_admin)]
)
async def product_create(category: int = Form(...),
name: str = Form(...),
slug: str = Form(...),
price: float = Form(...),
description: str = Form(...),
image: UploadFile = File(...)
) -> ProductInDB:
from ..crud import create_product
product = ProductCreate(category=category,
name=name,
slug=slug,
price=price,
description=description)
image_, thumb_image = await handle_file_upload(image)
product.image = image_
product.thumbnail = thumb_image
return await create_product(product=product)
Checking in the database:
ecommerce=# select image, thumbnail from product;
image | thumbnail
--------------------------------------+--------------------------------------------
76745b2aa81247fda66f8b086d96ac26.png | thumb_76745b2aa81247fda66f8b086d96ac26.png
(1 row)
So we have successfully saved our product's image and thumbnail file names and it is already served by our server:
I think from now, we are pretty done with backend product app and the next is to create the orders app or to add the tests to our product app.
The code changes for this episode -> episode-13 (opens new window)