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])
2
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:
And here's my tasks.py:
I hope someone finds this helpful. And anybody has any suggestions please let me know in the comments. Have a nice day;)
Hello everyone earlier I posted a solution for this question and even though that solution worked properly, I found a better solution.
Encoding and Decoding binary files using base64 makes them larger and that is not something we want. So a better solution is to temporarily save the uploaded file on the disk, pass the path to our celery worker to upload it and create a ProductImage instance in our database and then delete the file we saved on the disk .
Here’s how to implement it:
tasks.py:
In serializers.py you should override the create method of the ProductImage serializer like this:
You should also override the create method in ProductImage’s ViewSet to provide the image file for your serializer’s context: