Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use Celery to upload files in Django

I was wondering how I can use Celery workers to handle file uploads. So I tried implementing it on a simple class. I overrided the create class in my ModelViewSet. But apparently Django's default json encoder does not serialize ImageFields (Lame). I'll really appreciate it if you guys could tell me how I can fix this. Here is what I came up with:

serializers.py:

class ProductImageSerializer(serializers.ModelSerializer):
    class Meta:
        model = ProductImage
        fields = ['id', 'image']

tasks.py:

from time import sleep
from celery import shared_task
from .models import ProductImage

@shared_task:
def upload_image(product_id, image):
    print('Uploading image...')
    sleep(10)
    product = ProductImage(product_id=product_id, image=image)
    product.save()

views.py:

class ProductImageViewSet(ModelViewSet):
    serializer_class = ProductImageSerializer

    def get_queryset(self):
        return ProductImage.objects.filter(product_id=self.kwargs['product_pk'])

    def create(self, request, *args, **kwargs):
        product_id = self.kwargs['product_pk']
        image = self.request.FILES['image']
        image.open()
        image_data = Image.open(image)
        upload_image.delay(product_id, image_data)

        return Response('Thanks')

and here's the my model containing my ImageField:

class ProductImage(models.Model):
    product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name='images')
    image = models.ImageField(upload_to='store/images', validators=[validate_image_size])
like image 784
BFX0.9 Avatar asked Nov 23 '25 20:11

BFX0.9


1 Answers

So I figured out a way to do this. Here's my solution:

The problem is that celery's default json encoder cannot serialize Images, InMemoryUploadedFile, ModelObjects and... So we need to pass it a value that is json serializable. In this case, we wanna serialize an Image. So what we can do is to convert our Image to bytes, then convert that bytes object to string, so we can send it to our celery task. After we received the string in our task, we can convert it back to an Image and upload it using celery.Many people on the internet suggested this solution but none of them provided any code. So here is the code for the example above, if you want to see it in action:

In my views.py I used a ModelViewSet and overrided the create method:

def create(self, request, *args, **kwargs):

        image = self.request.FILES['image'].read()

        byte = base64.b64encode(image)
        
        data = {
            'product_id': self.kwargs['product_pk'],
            'image': byte.decode('utf-8'),
            "name": self.request.FILES['image'].name
        }

        upload_image.delay(data=data)

        return Response('Uploading...')

And here's my tasks.py:

from time import sleep
from celery import shared_task
from .models import ProductImage
import PIL.Image as Image
import io
import base64
import os
from django.core.files import File

@shared_task
def upload_image(data):
    
    print('Uploading image...')
    
    sleep(10)
    
    product_id = data['product_id']

    byte_data = data['image'].encode(encoding='utf-8')
    b = base64.b64decode(byte_data)
    img = Image.open(io.BytesIO(b))
    img.save(data['name'], format=img.format)
    
    with open(data['name'], 'rb') as file:
        picture = File(file)

        instance = ProductImage(product_id=product_id, image=picture)
        instance.save()
    
    os.remove(data['name'])

    print('Uploaded!')

I hope someone finds this helpful. And anybody has any suggestions please let me know in the comments. Have a nice day;)

like image 131
BFX0.9 Avatar answered Nov 26 '25 11:11

BFX0.9



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!